Skip to content

Conversation

@luckyPipewrench
Copy link
Collaborator

@luckyPipewrench luckyPipewrench commented Oct 31, 2025

Ports and expands #254 ideas to the new Providers Factory from #250, making SimpleFin more reliable, user-friendly, and secure. Addresses duplicate transactions (#110) and adds tools for common pain points like flaky syncs and account relinks. Builds on multi-provider support with guards to prevent "Other accounts" leaks.
Key Changes

Provider Framework Integration:

SimpleFin via Provider::SimplefinAdapter with centralized persistence in Account::ProviderImportAdapter.
Multi-provider ready: Auto-maintain AccountProvider links; guard against duplicates per type.

Sync Engine Enhancements:

Chunked first-sync (60-day with discovery) + 7-day buffer for regulars to respect Bridge limits.
Per-account error skipping with sync_stats; no full aborts.
Rate-limit detection (rate_limited_at timestamp) with UI feedback.
Balances-only mode: Quick refresh (updates balances/last_synced_at without transactions) via param; new controller action and dev-only card button (retainable in prod).
Optimized the balances import to run in the background for snappier UI

Transaction Dedup/Merging:

Matching: external_id + source → FITID → composite (±7d date, ±$0.01 amount, normalized names).
Pending→posted merges: Update existing entry, set transactions.was_merged = true, add note.
UI: Subtle "Merged" badge with tooltip.

UI Polish:

SimpleFin card badges: Unlinked/Skipped/Total; Rate-limited (time-ago tooltip).
Categorized error tooltips (auth/api/network/other) with samples.
"View errors" modal: Per-account lists + category counts.
Auto-relink on re-add (heuristics: last4/balance/name) to prevent orphans.
Manual relink modal: "Link existing accounts" button lists pairs; applies with dupe cleanup (skip/delete entries/holdings), data migration, and redundant account removal.

Rake Tasks & Utilities:

sure:simplefin:encrypt_access_urls: Idempotent backfill (dry-run/batch/limit, credentials-gated).
sure:backfill_was_merged: Heuristic for legacy pendings (dry-run/live).
Fixed private method crash; safe badge rendering.

Tests: 24+ green for adapters/import/UI; 100+ assertions covering chunking/skips/dedup/merges/relinks.

Impact

Smoother onboarding/syncs, fewer dupes from pendings/relinks.
Better handles Bridge quirks (quotas, errors) without overwhelming UI.
Future-proof: Aligns with #250's API layer TODO; extensible for extras like custom currencies.

Requesting reviews from @sokie and @jjmata. Fixes #110, #303 ; closes #254 (draft).

Summary by CodeRabbit

  • New Features

    • Added balances-only sync functionality for faster per-account balance updates.
    • Added error viewing modal to display latest SimpleFIN sync errors.
    • Enhanced transaction details display with provider metadata and additional fields.
    • Improved account linking workflows with modal-based relink flow and duplicate detection.
    • Added day-change tracking for holdings.
  • Bug Fixes

    • Improved race condition handling in automatic transfer matching.
    • Enhanced duplicate account detection and error categorization during syncs.

Note

Adds SimpleFin relink and balances-only sync, improves dedup/merge and pending→posted handling with richer UI/status and metadata, plus DB/index changes and rake utilities.

  • SimpleFin backend & sync:
    • Add balances-only sync path and stats via SimplefinItem::Importer#import_balances_only and SimplefinItem::Syncer (rate-limit detection, per-account error buckets, API request tracking).
    • Enhance first/regular sync (chunked discovery, pending include, unbounded discovery) and snapshot persistence; guard/cleanup via dedup_simplefin_accounts! and merge_duplicate_provider_accounts!.
    • New relink flow: compute candidates, modal UI, and apply_relink to migrate entries/holdings and move provider links; auto-open via auto_relink_controller.js and open_relink_for.
    • Safer deletion: SimplefinItem::Unlinker unlinks provider links before destroy.
  • Transactions & holdings:
    • Strengthen de‑dupe/merge in Account::ProviderImportAdapter#import_transaction (external_id→FITID→composite; mark transactions.was_merged; persist transaction.extra; notes, date heuristics by account type).
    • Add holdings day-change display and improved SimpleFin holdings processing (crypto symbol namespace, robust field fallbacks).
    • Improve auto transfer matching idempotency (find_or_create_by! with RecordNotUnique rescue).
  • Controllers, views, routes:
    • SimplefinItemsController: new actions balances, errors, relink, apply_relink; enhanced create/update/destroy/setup_accounts flows.
    • UI: SimpleFin card badges (unlinked/skipped/total, rate-limited), sync summary panel, errors modal, relink modal; auto-open relink; transaction merged badge and "Additional details" panel.
    • Routes for new actions; helper simplefin_error_tooltip.
  • Data & migrations:
    • Add transactions.was_merged and transactions.extra (GIN index).
    • Add ON DELETE CASCADE FKs for entries, holdings, account_providers; schema updated.
    • Minor config: config/database.yml username key.
  • Rake tasks:
    • Backfills/fixes: sure:backfill_was_merged, sure:simplefin:fix_was_merged, sure:simplefin:backfill_extra, holdings backfill, unlink, encrypt access URLs, debug and cleanup utilities.
  • Tests:
    • Expanded coverage for relink, dedupe, balances-only, pending→posted merge, extra metadata, holdings deletion rules, turbo responses.

Written by Cursor Bugbot for commit e84ea59. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link

coderabbitai bot commented Oct 31, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR significantly enhances the SimpleFIN sync pipeline with improved account linking workflows, balances-only syncs, per-account error resilience, encrypted access URLs, transaction metadata enrichment, and comprehensive UI updates. It refactors controllers with Turbo Streams support, introduces provider-linking logic, expands holdings and transaction processing, adds database migrations for schema changes, and includes new debugging and backfill tasks.

Changes

Cohort / File(s) Change Summary
SimpleFIN Controller & Routing
app/controllers/simplefin_items_controller.rb, config/routes.rb, app/controllers/concerns/simplefin_items/maps_helper.rb
New actions: balances (balances-only sync), errors (sync error display); enhanced update/create with Turbo Streams; refactored complete_account_setup/destroy with provider unlinking; new select_existing_account and link_existing_account for manual account linking; includes MapsHelper concern for pre-computing per-item UI maps; added post :balances and get :errors member routes.
SimpleFIN Models - Core
app/models/simplefin_item.rb, app/models/simplefin_item/unlinking.rb, app/models/simplefin_item/importer.rb, app/models/simplefin_item/syncer.rb, app/models/simplefin_account.rb
Added encryption_ready? class method and import_latest_simplefin_data(sync:) parameter; new SimplefinItem::Unlinking concern with idempotent unlink_all! method; SimplefinItem::Importer adds import_balances_only, sync parameter, rate-limit tracking, per-account error classification, and statistics persistence; SimplefinItem::Syncer implements balances-only fast path and multi-stage post-sync processing; SimplefinAccount adds uniqueness validation and ensure_account_provider! method.
SimpleFIN Processors
app/models/simplefin_account/processor.rb, app/models/simplefin_entry/processor.rb, app/models/simplefin_account/investments/holdings_processor.rb
Processor adds early return guard and ensure_account_provider! call; SimplefinEntry::Processor adds extra_metadata, notes, and reworked date-selection logic; HoldingsProcessor permits "Crypto" accountable_type, adds extensive logging, robust any\_of-based field lookups, computed-amount derivation, and crypto symbol prefixing.
Database Migrations
db/migrate/20251029190000_add_extra_to_transactions.rb, db/migrate/20251030172500_add_cascade_on_account_deletes.rb, db/migrate/20251102143510_remove_duplicate_account_providers_index.rb, db/migrate/20251103185320_drop_was_merged_from_transactions.rb, db/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rb, db/migrate/20251115194500_allow_null_merchant_id_on_recurring_transactions.rb, db/schema.rb
Adds extra jsonb column to transactions with GIN index; enforces ON DELETE CASCADE for account relationships; removes legacy account-providers index; drops was_merged boolean column; adds unique index on (simplefin_item_id, account_id) with null filter; allows nullable merchant_id on recurring_transactions; updates schema version and recurring_transactions indices.
SimpleFIN Views
app/views/simplefin_items/_simplefin_item.html.erb, app/views/simplefin_items/errors.html.erb, app/views/simplefin_items/select_existing_account.html.erb, app/views/simplefin_items/edit.html.erb, app/views/simplefin_items/new.html.erb, app/views/simplefin_items/setup_accounts.html.erb, app/views/settings/providers/_simplefin_panel.html.erb
Enhanced _simplefin_item.html.erb with sync stat badges, error display, duplicate handling, and collapsible sync summary; new errors.html.erb modal for error display; refactored select_existing_account.html.erb with conditional no-accounts messaging and improved account list UI; wrapped edit.html.erb in turbo-frame; replaced new.html.erb modal with standalone form; updated setup_accounts.html.erb cancel link; new _simplefin_panel.html.erb for provider settings.
Accounts & Settings Views
app/views/accounts/index.html.erb, app/views/accounts/_account.html.erb, app/views/accounts/index/_account_groups.erb, app/views/settings/_settings_nav.html.erb, app/views/settings/providers/show.html.erb, app/views/simplefin_items/index.html.erb, app/views/simplefin_items/show.html.erb
Wrapped manual accounts in id="manual-accounts" div; broadened "link provider" condition to include "Investment" accountable_type; updated account partial rendering syntax; removed SimpleFin from advanced settings nav; added SimpleFin section in provider settings; deleted index and show SimpleFin views.
Transaction Views & Helpers
app/views/transactions/show.html.erb, app/views/holdings/_holding.html.erb, app/helpers/transactions_helper.rb, app/helpers/simplefin_items_helper.rb
Added extra-details drawer for SimpleFIN metadata display; refined cost-basis display in holdings; new build_transaction_extra_details helper; new simplefin_error_tooltip helper for compact error summaries.
Controllers - Accounts & Settings
app/controllers/accounts_controller.rb, app/controllers/settings/providers_controller.rb, app/controllers/settings/bank_sync_controller.rb
Refined manual accounts retrieval with visible_manual scope; added per-item precomputation maps; added prepare_show_context helper; updated SimpleFIN provider entry capitalization and external link.
Models - Core
app/models/account.rb, app/models/account/provider_import_adapter.rb, app/models/holding.rb, app/models/family/auto_transfer_matchable.rb
Added visible_manual scope to Account; extended import_transaction with optional notes and extra parameters; new day_change method on Holding for trend computation; improved auto_match_transfers! with race-condition handling via find_or_create_by!.
Jobs & Services
app/jobs/simplefin_item/balances_only_job.rb, app/services/simplefin_item/unlinker.rb, lib/simplefin/date_utils.rb
New BalancesOnlyJob for background balances-only syncs with Turbo Streams broadcasting; deprecated thin-wrapper Unlinker service; new Simplefin::DateUtils.parse_provider_date utility.
Rake Tasks
lib/tasks/simplefin.rake, lib/tasks/simplefin_backfill.rake, lib/tasks/simplefin_debug.rake, lib/tasks/simplefin_holdings_backfill.rake, lib/tasks/simplefin_unlink.rake, lib/tasks/holdings_tools.rake
New encryption backfill task sure:simplefin:encrypt_access_urls; new backfill_extra and cleanup_demo_entries for demo cleanup; new tx_debug and entries_debug tasks for troubleshooting; new backfill_holdings task; new unlink_item task; new seed_prev_snapshot and remove_snapshot tasks for holdings demo.
Tests
test/controllers/simplefin_items_controller_test.rb, test/models/simplefin_entry/processor_test.rb, test/models/simplefin_item_dedupe_test.rb, test/views/transactions/merged_badge_view_test.rb, test/controllers/accounts_controller_test.rb, test/services/simplefin_item/unlinker_test.rb, test/models/simplefin_item/importer_duplicate_test.rb, test/models/account/provider_import_adapter_test.rb, test/models/recurring_transaction_test.rb, test/controllers/settings/providers_controller_test.rb, test/controllers/settings/hostings_controller_test.rb
Expanded simplefin_items_controller_test with new action coverage; new processor_test for extra metadata; new dedupe and duplicate-handling tests; new unlinker service tests; updated provider/hosting controller tests to use configuration stubs; adjusted fixture dates; added conditional skips for nullable merchant_id.
Configuration & Localization
config/locales/views/simplefin_items/update.en.yml, app/views/settings/providers/_provider_form.html.erb
New localization keys for update success/errors; updated provider status indicator color class.

Sequence Diagram(s)

sequenceDiagram
    participant UI as User/UI
    participant Ctrl as SimplefinItemsController
    participant Importer as SimplefinItem::Importer
    participant Provider as Provider API
    participant DB as Database
    participant Job as BalancesOnlyJob
    
    Note over UI,Job: Balances-Only Sync Flow (New)
    UI->>Ctrl: POST /simplefin_items/:id/balances
    Ctrl->>Ctrl: Create Sync with balances_only flag
    Ctrl->>Job: Enqueue BalancesOnlyJob
    Ctrl->>UI: Return sync_id (JSON/Redirect)
    
    Job->>Importer: import_balances_only
    Importer->>Provider: Discover accounts (minimal)
    Provider-->>Importer: Account list
    Importer->>Importer: Snapshot balances for linked accounts only
    Importer->>DB: Upsert minimal account + balance update
    Importer->>DB: Persist sync stats
    Job->>DB: Update last_synced_at
    Job->>UI: Broadcast Turbo Streams update
    
    Note over UI,Job: Full Sync Flow (Enhanced)
    UI->>Ctrl: POST /simplefin_items/:id (traditional)
    Ctrl->>Importer: import_latest_simplefin_data(sync:)
    Importer->>Importer: Per-account error handling
    Importer->>DB: Classify & track errors
    Importer-->>Ctrl: Partial success + stats
    Ctrl->>UI: Render with error summary
    
    Note over UI,Job: Manual Account Linking Flow (New)
    UI->>Ctrl: GET select_existing_account
    Ctrl->>Ctrl: Filter available SFAs
    Ctrl->>UI: Render modal with account list
    UI->>Ctrl: POST link_existing_account
    Ctrl->>DB: Create provider link (AccountProvider)
    Ctrl->>Importer: Recompute relink candidates
    Ctrl->>UI: Broadcast Turbo Streams (add account row)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Areas requiring extra attention:

  • SimplefinItem::Importer (app/models/simplefin_item/importer.rb): Dense logic for balances-only path, per-account error classification, stats tracking, and race-condition handling in find_or_create_by!. Verify error categorization and partial-success semantics.
  • SimplefinItems::MapsHelper (app/controllers/concerns/simplefin_items/maps_helper.rb): Complex multi-query batching for UI map precomputation; check N+1 mitigation and error handling in compute_duplicate_only_flag.
  • HoldingsProcessor (app/models/simplefin_account/investments/holdings_processor.rb): Extensive refactoring with new any_of helper, crypto normalization, and fallback price derivation; verify security/account type handling and computed-amount edge cases.
  • SimplefinEntry::Processor (app/models/simplefin_entry/processor.rb): New date-selection logic with account-type-dependent branching; ensure date precedence is correct for all account types.
  • Controller Refactoring (app/controllers/simplefin_items_controller.rb): New relink logic with compute_relink_candidates, Turbo Streams rendering, and modal-based workflows; verify provider-linking side effects and account transfer scenarios.
  • Database Migrations: Cascading deletes and partial unique indexes require verification of constraint semantics and potential data cleanup impact.
  • Turbo Streams Broadcasting: Multiple broadcast calls across jobs and controllers; ensure frame targets and partial rendering are correct and handle failures gracefully.

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • sokie

Poem

🐰 A SimpleFIN Stanza

Balances leap without full delay,
Errors gracefully skip the fray,
Modal links bind accounts just right,
Provider chains glow metadata-bright,
This hare hops through encrypted ways! 🔐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Simplefin enhancements v2' is concise and clearly indicates the main focus: comprehensive SimpleFin feature additions and improvements, matching the substantial changeset.
Linked Issues check ✅ Passed All coding requirements from #110 (robust initial sync, account creation) and #254 (60-day chunking, per-account error skipping, UI states, dedup/merge, encryption) are implemented: chunked discovery, error handling with stats, inline UI badges, transaction merge logic, extra/notes fields, and encryption tasks.
Out of Scope Changes check ✅ Passed All changes are scoped to SimpleFin enhancements, provider framework integration, sync/import reliability, transaction handling, relink UX, and supporting infrastructure; no unrelated system changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@luckyPipewrench luckyPipewrench marked this pull request as ready for review October 31, 2025 02:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (18)
lib/tasks/simplefin_debug.rake (5)

16-18: Use SQL-side, case-insensitive search for account match (safer, faster).

Avoid Ruby .find scans and substring checks; sanitize and delegate to DB.

-      sfa = item.simplefin_accounts.order(updated_at: :desc).find do |acc|
-        acc.name.to_s.downcase.include?(args[:account_name].to_s.downcase)
-      end
+      pattern = "%#{ActiveRecord::Base.sanitize_sql_like(args[:account_name].to_s.downcase)}%"
+      sfa = item.simplefin_accounts
+               .where("LOWER(name) LIKE ?", pattern)
+               .order(updated_at: :desc)
+               .first

71-73: Same here: DB search instead of Ruby scan for Account.

This avoids loading all accounts and mismatches.

-      acct = Account.order(updated_at: :desc).find do |a|
-        a.name.to_s.downcase.include?(args[:account_name].to_s.downcase)
-      end
+      pattern = "%#{ActiveRecord::Base.sanitize_sql_like(args[:account_name].to_s.downcase)}%"
+      acct = Account.where("LOWER(name) LIKE ?", pattern)
+                    .order(updated_at: :desc)
+                    .first

83-94: Preload entryable to avoid N+1 when accessing was_merged.

Accessing e.entryable per row will issue extra queries without preload.

-      entries = acct.entries.where(entryable_type: "Transaction").order(date: :desc).limit(limit)
+      entries = acct.entries
+                    .where(entryable_type: "Transaction")
+                    .includes(:entryable)
+                    .order(date: :desc)
+                    .limit(limit)

45-55: PII caution: payee/description/memo in plaintext JSON.

These can contain sensitive data. Consider an ENV flag to redact or skip these fields for safer logs, or ensure guidance that this task is dev-only.

Would you like a small redaction helper (e.g., mask middle of strings unless ENV["VERBOSE"]=="1")?


59-61: Avoid leaking backtraces outside dev.

Printing backtraces can expose internals. Gate by environment or ENV flag.

-    rescue => e
-      puts({ error: e.class.name, message: e.message, backtrace: e.backtrace&.take(3) }.to_json)
+    rescue => e
+      payload = { error: e.class.name, message: e.message }
+      payload[:backtrace] = e.backtrace&.take(3) if Rails.env.development? || ENV["DEBUG"] == "1"
+      puts(payload.to_json)
       exit 1
     end

Also applies to: 97-99

lib/tasks/simplefin_backfill.rake (7)

42-44: Use zone-aware today for consistency.

Prefer Time.zone.today over Date.today to align with ActiveSupport time math.

-      window_start = days_i.days.ago.to_date
-      window_end   = Date.today
+      window_start = days_i.days.ago.to_date
+      window_end   = Time.zone.today

88-114: Consolidate date parsing; handle ms epochs.

Current parsing is duplicated and assumes seconds. Add a helper to normalize String/Numeric/Date/Time and auto-downgrade millisecond epochs.

-            posted_d =
-              case posted
-              when String
-                Date.parse(posted) rescue nil
-              when Numeric
-                Time.zone.at(posted).to_date rescue nil
-              when Date
-                posted
-              when Time, DateTime
-                posted.to_date
-              else
-                nil
-              end
-            trans_d =
-              case trans
-              when String
-                Date.parse(trans) rescue nil
-              when Numeric
-                Time.zone.at(trans).to_date rescue nil
-              when Date
-                trans
-              when Time, DateTime
-                trans.to_date
-              else
-                nil
-              end
+            to_date = ->(val) do
+              case val
+              when String
+                Date.parse(val) rescue nil
+              when Numeric
+                ts = val.to_i
+                ts = ts > 10**11 ? ts / 1000 : ts # ms → s
+                Time.zone.at(ts).to_date rescue nil
+              when Date
+                val
+              when Time, DateTime
+                val.to_date
+              else
+                nil
+              end
+            end
+            posted_d = to_date.call(posted)
+            trans_d  = to_date.call(trans)

145-146: Avoid send on private methods; expose a public matcher API.

Relying on processor.send(:name/:amount) is brittle. Prefer a public method (e.g., processor.matching_attrs) returning { name:, amount:, date: }, then use it in both dry-run and write paths.

Would you add such a method to SimplefinEntry::Processor and update the call here?


156-159: Add context to error logs.

Include sfa_id, account_id, and tx_id for triage; optionally gate backtraces behind ENV.

-          rescue => e
-            total_errors += 1
-            puts({ error: e.class.name, message: e.message }.to_json)
+          rescue => e
+            total_errors += 1
+            puts({ error: e.class.name, message: e.message, sfa_id: sfa.id, account_id: acct.id, tx_id: t[:id] }.to_json)
+            puts({ backtrace: e.backtrace.first(3) }.to_json) if ENV["VERBOSE_ERRORS"] == "1"

124-131: Force semantics—clarify overwrite behavior.

force proceeds even when extra_hash is nil but ultimately counts as skipped if no extra is produced. If intent is “overwrite existing extra regardless,” add a branch to write/merge an empty "simplefin" hash or explicitly bypass overwrite when no metadata is derivable.


168-176: Cleanup patterns: trim parts; optional case-insensitive match.

Whitespace around | splits isn’t trimmed; also consider ILIKE for Postgres.

-      patterns = pattern.split("|")
+      patterns = pattern.split("|").map { |p| p.strip }
-      like_sql = patterns.map { |p| "external_id LIKE ?" }.join(" OR ")
+      like_sql = patterns.map { |_p| "external_id LIKE ?" }.join(" OR ")

If your DB is Postgres and you want case-insensitive matches:

like_sql = patterns.map { |_p| "external_id ILIKE ?" }.join(" OR ")

Also applies to: 190-196


20-24: Operational note: backfills can trigger Account#sync_data side effects.

Ensure bulk writes won’t thrash sync pipelines. Consider batching (args: LIMIT/BATCH_SIZE), pausing auto-sync callbacks, or scheduling during low traffic.

Based on learnings

Also applies to: 124-131, 149-155

config/routes.rb (1)

5-13: Consolidate the SimpleFin routes into the existing resources block.

Defining resources :simplefin_items twice generates duplicate REST routes for the same controller, which makes the routing table noisier and harder to maintain. Fold the new member actions into the existing block down at Line 277 so everything lives in one definition.

-  resources :simplefin_items do
-    member do
-      get :errors
-      post :balances
-      get :relink
-      post :apply_relink
-      get :prelink
-    end
-  end
@@
   resources :simplefin_items, only: %i[index new create show edit update destroy] do
     member do
       post :sync
       get :setup_accounts
       post :complete_account_setup
+      get :errors
+      post :balances
+      get :relink
+      post :apply_relink
+      get :prelink
     end
   end
test/models/simplefin_item_dedupe_test.rb (1)

15-54: Strengthen assertion and consider testing data migration.

The test correctly sets up duplicate SimplefinAccounts and verifies deduplication, but there are opportunities for improvement:

  1. Line 49: The assertion assert_operator stats[:deleted_simplefin_accounts], :>=, 1 is weaker than it should be. Since you created exactly 2 duplicate SFAs (lines 17-32), you should assert exactly 1 deletion:
-    assert_operator stats[:deleted_simplefin_accounts], :>=, 1
+    assert_equal 1, stats[:deleted_simplefin_accounts], "Expected exactly 1 duplicate SFA to be deleted"
  1. Data migration coverage: The dedup_simplefin_accounts! method (shown in relevant snippets) also migrates entries and holdings from duplicate accounts. Consider adding assertions to verify:

    • stats[:moved_entries]
    • stats[:moved_holdings]
    • stats[:merged_accounts]

    Or create a separate test that exercises the data migration path by adding entries/holdings to the duplicate account before deduplication.

The current test provides basic coverage but could be more comprehensive for a critical deduplication operation.

test/views/transactions/merged_badge_view_test.rb (2)

14-22: Consider extracting transaction creation to fixtures.

Creating the transaction and entry inline (lines 14-22) works for this simple case, but as the guidelines state: "For tests needing many records, use Rails helpers to construct data and inline the creation in the test."

Since this is a minimal setup (2 records), inline creation is acceptable. However, if multiple tests need merged transactions, consider adding a fixture for reusability.


24-27: Consider more robust HTML assertions.

The assertion assert_includes html, "Merged from pending to posted" verifies the text is present but doesn't validate the HTML structure (e.g., presence of tooltip element, badge styling, etc.).

For a more robust test, consider using Capybara's CSS selectors or Nokogiri to assert on specific elements:

doc = Nokogiri::HTML(html)
badge = doc.at_css('[data-controller="tooltip"]')
assert badge, "Expected tooltip badge to be present"
assert_includes badge.text, "Merged", "Expected badge text"

This ensures the UI structure is correct, not just the text content.

lib/tasks/simplefin_unlink.rake (1)

18-21: Add ID format validation.

The task validates presence but not format of item_id. Consider adding a basic UUID validation to provide clearer error messages:

unless item_id.present? && item_id.match?(/\A[a-f0-9\-]{36}\z/i)
  puts({ ok: false, error: "invalid_item_id", message: "item_id must be a valid UUID" }.to_json)
  exit 1
end

This will catch obvious typos before hitting the database and provide better feedback.

app/views/transactions/show.html.erb (1)

101-146: Move presentation logic to a helper method per coding guidelines.

The section violates the guideline: "Keep heavy logic out of ERB views; prefer helpers/components instead." The respond_to?(:extra) check is unnecessary—the schema enforces NOT NULL with default: {}, so the column always exists.

Additionally, line 142's rescue clause silently catches exceptions without logging, making debugging difficult. Rails 7.2 properly escapes attribute content in title="<%= v %>", so the XSS concern is mitigated by default.

Refactor to app/helpers/transactions_helper.rb:

def format_transaction_extra(transaction)
  return nil unless transaction.extra.present?
  
  extra = transaction.extra
  sf = extra.is_a?(Hash) ? extra["simplefin"] : nil
  
  if sf.present?
    {
      type: :simplefin,
      fields: extract_simplefin_fields(sf),
      provider_extras: sf["extra"].is_a?(Hash) ? sf["extra"] : {}
    }
  else
    {
      type: :raw,
      content: JSON.pretty_generate(extra)
    }
  rescue JSON::GeneratorError => e
    Rails.logger.warn("Failed to format transaction extra: #{e.message}")
    { type: :raw, content: extra.to_s }
  end
end

private

def extract_simplefin_fields(sf)
  %w[payee description memo].each_with_object({}) do |field, hash|
    hash[field] = sf[field] if sf[field].present?
  end
end

Then simplify the view to render formatted data via a component. Remove the respond_to? check since extra is guaranteed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da114b5 and c17f44a.

📒 Files selected for processing (40)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/models/account/provider_import_adapter.rb (2 hunks)
  • app/models/family/auto_transfer_matchable.rb (1 hunks)
  • app/models/holding.rb (1 hunks)
  • app/models/simplefin_account.rb (1 hunks)
  • app/models/simplefin_account/investments/holdings_processor.rb (4 hunks)
  • app/models/simplefin_account/processor.rb (1 hunks)
  • app/models/simplefin_entry/processor.rb (3 hunks)
  • app/models/simplefin_item.rb (3 hunks)
  • app/models/simplefin_item/importer.rb (6 hunks)
  • app/models/simplefin_item/syncer.rb (2 hunks)
  • app/services/simplefin_item/unlinker.rb (1 hunks)
  • app/views/accounts/index.html.erb (1 hunks)
  • app/views/holdings/_holding.html.erb (1 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (3 hunks)
  • app/views/simplefin_items/errors.html.erb (1 hunks)
  • app/views/simplefin_items/relink.html.erb (1 hunks)
  • app/views/transactions/_transaction.html.erb (1 hunks)
  • app/views/transactions/show.html.erb (1 hunks)
  • config/database.yml (1 hunks)
  • config/locales/views/simplefin_items/en.yml (1 hunks)
  • config/locales/views/simplefin_items/update.en.yml (1 hunks)
  • config/routes.rb (1 hunks)
  • db/migrate/20251029015700_add_was_merged_to_transactions.rb (1 hunks)
  • db/migrate/20251029190000_add_extra_to_transactions.rb (1 hunks)
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1 hunks)
  • db/schema.rb (6 hunks)
  • lib/tasks/holdings_tools.rake (1 hunks)
  • lib/tasks/simplefin.rake (1 hunks)
  • lib/tasks/simplefin_backfill.rake (1 hunks)
  • lib/tasks/simplefin_debug.rake (1 hunks)
  • lib/tasks/simplefin_fix_was_merged.rake (1 hunks)
  • lib/tasks/simplefin_holdings_backfill.rake (1 hunks)
  • lib/tasks/simplefin_unlink.rake (1 hunks)
  • lib/tasks/was_merged.rake (1 hunks)
  • test/controllers/simplefin_items_controller_test.rb (4 hunks)
  • test/models/account/provider_import_adapter_test.rb (7 hunks)
  • test/models/simplefin_entry/processor_test.rb (1 hunks)
  • test/models/simplefin_item_dedupe_test.rb (1 hunks)
  • test/views/transactions/merged_badge_view_test.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (22)
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/views/accounts/index.html.erb
  • app/models/simplefin_account/processor.rb
  • app/models/holding.rb
  • app/models/simplefin_account.rb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • app/models/account/provider_import_adapter.rb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • app/models/simplefin_item/syncer.rb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/transactions/show.html.erb
  • app/views/simplefin_items/relink.html.erb
config/**

📄 CodeRabbit inference engine (AGENTS.md)

Store application and environment configuration under config/

Files:

  • config/locales/views/simplefin_items/update.en.yml
  • config/routes.rb
  • config/database.yml
  • config/locales/views/simplefin_items/en.yml
app/models/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods

Use ActiveRecord validations for forms and complex domain constraints.

app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required

Files:

  • app/models/simplefin_account/processor.rb
  • app/models/holding.rb
  • app/models/simplefin_account.rb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/models/simplefin_item/importer.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • app/models/simplefin_item/syncer.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/models/simplefin_account/processor.rb
  • app/models/holding.rb
  • app/models/simplefin_account.rb
  • app/services/simplefin_item/unlinker.rb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • app/models/simplefin_item/syncer.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/models/simplefin_account/processor.rb
  • config/routes.rb
  • app/models/holding.rb
  • db/migrate/20251029190000_add_extra_to_transactions.rb
  • test/views/transactions/merged_badge_view_test.rb
  • app/models/simplefin_account.rb
  • app/services/simplefin_item/unlinker.rb
  • test/models/account/provider_import_adapter_test.rb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • test/models/simplefin_item_dedupe_test.rb
  • app/models/account/provider_import_adapter.rb
  • test/controllers/simplefin_items_controller_test.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • test/models/simplefin_entry/processor_test.rb
  • app/models/simplefin_item/syncer.rb
  • db/migrate/20251029015700_add_was_merged_to_transactions.rb
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb
  • db/schema.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/models/simplefin_account/processor.rb
  • config/routes.rb
  • app/models/holding.rb
  • db/migrate/20251029190000_add_extra_to_transactions.rb
  • test/views/transactions/merged_badge_view_test.rb
  • app/models/simplefin_account.rb
  • app/services/simplefin_item/unlinker.rb
  • test/models/account/provider_import_adapter_test.rb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • test/models/simplefin_item_dedupe_test.rb
  • app/models/account/provider_import_adapter.rb
  • test/controllers/simplefin_items_controller_test.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • test/models/simplefin_entry/processor_test.rb
  • app/models/simplefin_item/syncer.rb
  • db/migrate/20251029015700_add_was_merged_to_transactions.rb
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb
  • db/schema.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/models/simplefin_account/processor.rb
  • app/models/holding.rb
  • app/models/simplefin_account.rb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/models/simplefin_item/importer.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • app/models/simplefin_item/syncer.rb
db/migrate/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place database migrations under db/migrate/

Simple validations like null checks and unique constraints should be handled by database constraints in migration files.

Files:

  • db/migrate/20251029190000_add_extra_to_transactions.rb
  • db/migrate/20251029015700_add_was_merged_to_transactions.rb
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb
db/migrate/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Enforce simple validations in the database (NULL constraints, unique indexes, etc.)

Files:

  • db/migrate/20251029190000_add_extra_to_transactions.rb
  • db/migrate/20251029015700_add_was_merged_to_transactions.rb
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb
test/**/*_test.rb

📄 CodeRabbit inference engine (AGENTS.md)

Name Minitest files with *_test.rb and mirror the app/ structure under test/

test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.

test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test

Files:

  • test/views/transactions/merged_badge_view_test.rb
  • test/models/account/provider_import_adapter_test.rb
  • test/models/simplefin_item_dedupe_test.rb
  • test/controllers/simplefin_items_controller_test.rb
  • test/models/simplefin_entry/processor_test.rb
app/services/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid creating service objects; do not add business-logic files under app/services

Files:

  • app/services/simplefin_item/unlinker.rb
app/views/**/_*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/_transaction.html.erb
  • app/views/holdings/_holding.html.erb
db/schema.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Treat db/schema.rb as the authoritative source of truth for model relationships

Files:

  • db/schema.rb
🧠 Learnings (40)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions

Applied to files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • test/controllers/simplefin_items_controller_test.rb
  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated

Applied to files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/errors.html.erb
  • test/controllers/simplefin_items_controller_test.rb
  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_account.rb
  • test/models/account/provider_import_adapter_test.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/models/simplefin_item.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • app/models/account/provider_import_adapter.rb
  • lib/tasks/simplefin_backfill.rake
  • app/models/simplefin_entry/processor.rb
  • app/models/family/auto_transfer_matchable.rb
  • lib/tasks/was_merged.rake
  • app/models/simplefin_item/syncer.rb
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_account.rb
  • test/models/account/provider_import_adapter_test.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_entry/processor.rb
  • test/models/simplefin_entry/processor_test.rb
  • db/schema.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_account.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/controllers/simplefin_items_controller.rb
  • test/models/simplefin_entry/processor_test.rb
  • db/schema.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_item.rb
  • app/controllers/simplefin_items_controller.rb
  • lib/tasks/was_merged.rake
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator

Applied to files:

  • app/models/holding.rb
  • test/models/account/provider_import_adapter_test.rb
  • lib/tasks/holdings_tools.rake
  • app/models/simplefin_account/investments/holdings_processor.rb
  • app/models/simplefin_item/importer.rb
  • app/views/holdings/_holding.html.erb
  • lib/tasks/simplefin_holdings_backfill.rake
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to db/migrate/**/*.rb : Enforce simple validations in the database (NULL constraints, unique indexes, etc.)

Applied to files:

  • db/migrate/20251029190000_add_extra_to_transactions.rb
  • app/models/simplefin_account.rb
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb
  • db/schema.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/models/simplefin_account.rb
  • app/controllers/simplefin_items_controller.rb
  • db/schema.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/models/simplefin_account.rb
  • app/controllers/simplefin_items_controller.rb
  • test/models/simplefin_item_dedupe_test.rb
  • app/models/family/auto_transfer_matchable.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/models/**/*.rb : Implement complex validations and business logic with ActiveRecord validations

Applied to files:

  • app/models/simplefin_account.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to db/migrate/*.rb : Simple validations like null checks and unique constraints should be handled by database constraints in migration files.

Applied to files:

  • app/models/simplefin_account.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.

Applied to files:

  • app/models/simplefin_account.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.

Applied to files:

  • app/models/simplefin_account.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations

Applied to files:

  • app/views/simplefin_items/errors.html.erb
  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/_*.html.erb : Name partials with a leading underscore (e.g., _form_errors.html.erb)

Applied to files:

  • app/views/simplefin_items/errors.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to app/views/**/_*.html.erb : Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Applied to files:

  • app/views/simplefin_items/errors.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use ViewComponents when elements have complex logic/styling, are reused across contexts, need variants/slots/accessibility, or require Stimulus interactivity

Applied to files:

  • app/views/simplefin_items/errors.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/transfer.rb : Transfer auto-matching must meet all criteria: different accounts, within 4 days, same currency, opposite values

Applied to files:

  • test/models/account/provider_import_adapter_test.rb
  • app/models/family/auto_transfer_matchable.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior

Applied to files:

  • test/models/account/provider_import_adapter_test.rb
  • test/models/simplefin_item_dedupe_test.rb
  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{entry,valuation,transaction,trade}.rb : Entry amounts are signed: negative = inflow, positive = outflow (applies to Entry and its types: Valuation, Transaction, Trade)

Applied to files:

  • test/models/account/provider_import_adapter_test.rb
  • app/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/models/simplefin_item.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • app/models/family/auto_transfer_matchable.rb
  • app/models/simplefin_item/syncer.rb
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_item/importer.rb
  • app/models/simplefin_item/syncer.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test

Applied to files:

  • test/models/simplefin_item_dedupe_test.rb
  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/**/*_test.rb : ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.

Applied to files:

  • test/models/simplefin_item_dedupe_test.rb
  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to test/**/*_test.rb : Name Minitest files with *_test.rb and mirror the app/ structure under test/

Applied to files:

  • test/models/simplefin_item_dedupe_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Never test the implementation details of one class in another class’s test suite

Applied to files:

  • test/models/simplefin_item_dedupe_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.turbo_stream.erb : Use Turbo Streams to enhance functionality but avoid hard dependencies on them

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-08-22T18:30:26.758Z
Learnt from: jjmata
Repo: we-promise/sure PR: 116
File: app/views/family_exports/_list.html.erb:16-28
Timestamp: 2025-08-22T18:30:26.758Z
Learning: In the family exports UI (app/views/family_exports/_list.html.erb), raw color utilities like bg-green-500/5, text-green-500, bg-red-500/5, text-red-500 are preferred over design system tokens for status indicators to maintain UI legibility and consistency with the existing import UI patterns. The user has indicated that design token replacements would make the UI illegible in this context.

Applied to files:

  • app/views/holdings/_holding.html.erb
📚 Learning: 2025-09-23T22:22:00.149Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:00.149Z
Learning: Applies to app/{views,components}/**/*.html.erb : In ERB views, wire Stimulus interactions declaratively using data-action to call controller methods

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to db/schema.rb : Treat db/schema.rb as the authoritative source of truth for model relationships

Applied to files:

  • db/schema.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to db/seeds.rb : Keep seed data definitions in db/seeds.rb

Applied to files:

  • db/schema.rb
📚 Learning: 2025-08-22T23:27:46.033Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: db/migrate/20250813144520_add_institution_fields_to_simplefin_items.rb:6-6
Timestamp: 2025-08-22T23:27:46.033Z
Learning: Institution URLs in financial contexts are typically short (under 100 characters) and don't require text columns - varchar(255) via :string is sufficient for SimpleFin institution URLs.

Applied to files:

  • db/schema.rb
🧬 Code graph analysis (15)
app/models/simplefin_account/processor.rb (1)
app/models/simplefin_account.rb (2)
  • current_account (16-18)
  • ensure_account_provider! (22-34)
db/migrate/20251029190000_add_extra_to_transactions.rb (1)
db/migrate/20251029015700_add_was_merged_to_transactions.rb (2)
  • change (1-5)
  • change (2-4)
app/services/simplefin_item/unlinker.rb (1)
app/models/simplefin_item/importer.rb (1)
  • initialize (5-9)
test/models/account/provider_import_adapter_test.rb (1)
app/models/account/provider_import_adapter.rb (2)
  • import_holding (232-294)
  • import_transaction (20-101)
app/models/simplefin_item.rb (3)
app/models/simplefin_item/provided.rb (1)
  • simplefin_provider (4-6)
app/models/simplefin_item/importer.rb (1)
  • import (11-32)
app/models/simplefin_account.rb (1)
  • current_account (16-18)
app/models/simplefin_account/investments/holdings_processor.rb (3)
app/models/simplefin_entry/processor.rb (6)
  • account (47-49)
  • currency (97-99)
  • import_adapter (43-45)
  • amount (78-95)
  • date (105-120)
  • external_id (55-59)
app/models/simplefin_account/transactions/processor.rb (1)
  • account (39-41)
app/models/account/provider_import_adapter.rb (1)
  • import_holding (232-294)
app/controllers/simplefin_items_controller.rb (6)
app/controllers/accounts_controller.rb (3)
  • before_action (1-85)
  • sync (30-46)
  • family (78-80)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (2)
  • dedup_simplefin_accounts! (163-226)
  • merge_duplicate_provider_accounts! (230-274)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/models/simplefin_account.rb (1)
  • current_account (16-18)
app/models/simplefin_item/importer.rb (3)
app/models/account/provider_import_adapter.rb (1)
  • update_balance (211-216)
app/models/provider/simplefin.rb (1)
  • get_accounts (27-74)
app/models/simplefin_item.rb (1)
  • upsert_simplefin_snapshot! (61-70)
test/models/simplefin_item_dedupe_test.rb (1)
app/models/simplefin_item.rb (1)
  • dedup_simplefin_accounts! (163-226)
app/models/account/provider_import_adapter.rb (3)
app/models/simplefin_entry/processor.rb (10)
  • external_id (55-59)
  • amount (78-95)
  • currency (97-99)
  • date (105-120)
  • name (61-76)
  • merchant (169-182)
  • notes (189-201)
  • posted_date (122-139)
  • transacted_date (141-158)
  • account (47-49)
app/models/plaid_entry/processor.rb (7)
  • external_id (33-35)
  • amount (41-43)
  • currency (45-47)
  • date (49-51)
  • name (37-39)
  • merchant (62-70)
  • account (29-31)
app/models/concerns/enrichable.rb (1)
  • enrich_attribute (26-28)
test/controllers/simplefin_items_controller_test.rb (2)
test/models/account_provider_test.rb (1)
  • setup (3-132)
test/models/simplefin_item_test.rb (1)
  • setup (3-148)
app/models/simplefin_entry/processor.rb (2)
app/models/account/provider_import_adapter.rb (2)
  • attr_reader (1-382)
  • to_date (153-158)
app/models/simplefin_account/transactions/processor.rb (2)
  • attr_reader (1-42)
  • account (39-41)
test/models/simplefin_entry/processor_test.rb (1)
app/models/simplefin_entry/processor.rb (3)
  • process (10-25)
  • name (61-76)
  • currency (97-99)
app/models/simplefin_item/syncer.rb (4)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (1)
  • import_latest_simplefin_data (41-43)
app/models/concerns/syncable.rb (1)
  • perform_post_sync (46-48)
app/models/sync.rb (1)
  • perform_post_sync (144-151)
db/migrate/20251029015700_add_was_merged_to_transactions.rb (1)
db/migrate/20251029190000_add_extra_to_transactions.rb (2)
  • change (1-6)
  • change (2-5)

luckyPipewrench pushed a commit to luckyPipewrench/sure that referenced this pull request Oct 31, 2025
…2). Address all 15 actionable CodeRabbit comments: Add UUID validations in rakes (e.g., simplefin_unlink), swap Ruby pattern matching/loops for efficient DB queries (e.g., where LOWER(name) LIKE ?), generate docstrings for low-coverage areas (31%), consolidate routes for simplefin_items, move view logic to helpers (e.g., format_transaction_extra), strengthen tests with exact assertions/fixtures for dedup/relink failures. Also, check for overlaps with merged we-promise#262 (merchants fix): Ensure merchant creation in simplefin_entry/processor.rb aligns with new payee-based flow and MD5 IDs; add tests for edge cases like empty payees or over-merging pendings. Prioritize security (PII redaction in logs, no hardcoded secrets).
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
lib/tasks/simplefin_fix_was_merged.rake (1)

35-36: Dry-run default has been corrected.

The previous concern about dry_run defaulting to destructive behavior has been addressed. The current implementation correctly defaults to true when no argument is provided, ensuring safe preview behavior.

lib/tasks/simplefin_unlink.rake (1)

9-21: The custom argument parsing has been flagged in a previous review.

The existing review comment suggests simplifying lines 9-15 by using Rake's native argument handling. That recommendation remains valid.

🧹 Nitpick comments (7)
.junie/guidelines.md (3)

20-20: Wrap bare URLs in markdown syntax to comply with lint rules.

The URLs on this line are reported as bare URLs (MD034). Wrap them in angle brackets or convert to markdown links for compliance.

- - cd sure
- - cp .env.local.example .env.local
- - bin/setup
- - bin/dev (visit http://localhost:3000; demo: [email protected] / Password1!)
+ - cd sure
+ - cp .env.local.example .env.local
+ - bin/setup
+ - bin/dev (visit <http://localhost:3000>; demo: [email protected] / Password1!)

39-39: Capitalize "GitHub" per standard naming conventions.

- - RuboCop is enforced by CI (`bin/rubocop -f github`)
+ - RuboCop is enforced by CI (`bin/rubocop -f GitHub`)

63-63: Use hyphen for compound adjective "request-scoped".

- - Use `Current.user` and `Current.family` for request scoped context (never `current_user`/`current_family`)
+ - Use `Current.user` and `Current.family` for request-scoped context (never `current_user`/`current_family`)
lib/tasks/simplefin_fix_was_merged.rake (1)

104-117: Consider extracting date parsing logic.

The date parsing logic for posted and transacted_at is duplicated. Extracting this into a helper method would improve maintainability and reduce code duplication.

Example helper method:

def parse_flexible_date(value)
  case value
  when String then Date.parse(value) rescue nil
  when Numeric then Time.zone.at(value).to_date rescue nil
  when Date then value
  when Time, DateTime then value.to_date
  else nil
  end
end

Then use:

posted_d = parse_flexible_date(t[:posted])
trans_d = parse_flexible_date(t[:transacted_at])
app/controllers/simplefin_items_controller.rb (3)

323-449: Consider extracting apply_relink to a service object.

The apply_relink method spans ~120 lines and handles multiple responsibilities: pair validation, transaction orchestration, data migration, cleanup, and response formatting. Extracting this logic to a dedicated service (e.g., SimplefinItem::RelinkService) would improve testability and maintainability.

Example structure:

# app/services/simplefin_item/relink_service.rb
class SimplefinItem::RelinkService
  def initialize(simplefin_item, pairs, family)
    @simplefin_item = simplefin_item
    @pairs = pairs
    @family = family
  end

  def call
    # Extract lines 329-409 here
  end
end

# In controller:
def apply_relink
  pairs = parse_relink_pairs
  results = SimplefinItem::RelinkService.new(@simplefin_item, pairs, Current.family).call
  # Response handling...
end

455-562: Move helper methods to a concern or service.

The private methods compute_unlinked_count, normalize_name, and compute_relink_candidates (lines 455-562) implement domain logic that could be reused or tested independently. Consider extracting them to a concern (e.g., SimplefinItems::RelinkHelpers) or incorporating them into a service object.

This would improve separation of concerns and make the controller more focused on request/response handling.


565-569: Simplify defensive Current.family checks.

The set_simplefin_item method includes defensive checks for Current.family (lines 565-568), but Current.family should always be available after authentication via before_action. If this is for test compatibility, document why; otherwise, simplify to just line 566.

 def set_simplefin_item
-  if defined?(Current) && Current.respond_to?(:family) && Current.family.present?
-    @simplefin_item = Current.family.simplefin_items.find(params[:id])
-  else
-    @simplefin_item = SimplefinItem.find(params[:id])
-  end
+  @simplefin_item = Current.family.simplefin_items.find(params[:id])
 end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c17f44a and 1fd3f32.

📒 Files selected for processing (11)
  • .junie/guidelines.md (1 hunks)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/helpers/simplefin_items_helper.rb (1 hunks)
  • app/javascript/controllers/auto_relink_controller.js (1 hunks)
  • app/views/accounts/index.html.erb (1 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (3 hunks)
  • app/views/simplefin_items/relink.html.erb (1 hunks)
  • config/routes.rb (1 hunks)
  • lib/tasks/simplefin_backfill.rake (1 hunks)
  • lib/tasks/simplefin_fix_was_merged.rake (1 hunks)
  • lib/tasks/simplefin_unlink.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/views/simplefin_items/relink.html.erb
  • lib/tasks/simplefin_backfill.rake
  • config/routes.rb
🧰 Additional context used
📓 Path-based instructions (21)
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/helpers/simplefin_items_helper.rb
  • app/controllers/simplefin_items_controller.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/helpers/simplefin_items_helper.rb
  • app/controllers/simplefin_items_controller.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/helpers/simplefin_items_helper.rb
  • app/javascript/controllers/auto_relink_controller.js
  • app/controllers/simplefin_items_controller.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/helpers/simplefin_items_helper.rb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/helpers/simplefin_items_helper.rb
  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/helpers/simplefin_items_helper.rb
  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/javascript/**/*.{js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

app/javascript/**/*.{js,jsx}: Place JavaScript code under app/javascript/
JavaScript naming: lowerCamelCase for variables/functions; PascalCase for classes/components
Let Biome format and lint JavaScript code (npm run lint/format)

Files:

  • app/javascript/controllers/auto_relink_controller.js
app/javascript/controllers/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/javascript/controllers/**/*.js: Stimulus controllers should remain lightweight, have fewer than 7 targets, use private methods and expose a clear public API.
Pass data via data-*-value attributes, not with inline JavaScript.
Component-specific controllers should stay near components; global controllers go in app/javascript/controllers/.

Files:

  • app/javascript/controllers/auto_relink_controller.js
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/javascript/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Use URL query params for state over localStorage/sessionStorage; only persist to DB if absolutely necessary

Files:

  • app/javascript/controllers/auto_relink_controller.js
app/javascript/controllers/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/javascript/controllers/**/*.{js,ts}: Stimulus controllers should display preformatted values; avoid implementing formatting logic client-side
Keep client-side code for interactions where it truly shines (e.g., bulk selection); avoid unnecessary client-only solutions

Place global Stimulus controllers under app/javascript/controllers so they can be used across any view

Files:

  • app/javascript/controllers/auto_relink_controller.js
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
{app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

{app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}}: Keep Stimulus controllers lightweight: aim for fewer than 7 targets; use private methods and expose a clear public API
Keep Stimulus controllers focused: no domain logic; favor single responsibility; leverage Stimulus callbacks, actions, targets, values, and classes

Files:

  • app/javascript/controllers/auto_relink_controller.js
{app/javascript/controllers,app/components}/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

Use Stimulus targets for DOM access instead of manual document.getElementById calls

Files:

  • app/javascript/controllers/auto_relink_controller.js
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/views/accounts/index.html.erb
  • app/controllers/simplefin_items_controller.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/accounts/index.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/_*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
🧠 Learnings (38)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
  • app/controllers/simplefin_items_controller.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Make changes atomic, testable, and explain their impact briefly in code suggestions.

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
  • lib/tasks/simplefin_unlink.rake
  • .junie/guidelines.md
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:22:07.608Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:07.608Z
Learning: Applies to {app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb} : Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:22:07.608Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:07.608Z
Learning: Applies to {app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}} : Keep Stimulus controllers focused: no domain logic; favor single responsibility; leverage Stimulus callbacks, actions, targets, values, and classes

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to app/javascript/controllers/**/*.{js,ts} : Place global Stimulus controllers under app/javascript/controllers so they can be used across any view

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:22:00.149Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:00.149Z
Learning: Applies to **/*_controller.{js,ts} : In Stimulus controllers, do not imperatively bind DOM events (e.g., addEventListener in connect); expose methods and let views trigger them via actions

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/javascript/controllers/**/*.js : Global Stimulus controllers under app/javascript/controllers/ can be used across any view

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/javascript/controllers/**/*.js : Stimulus controllers should remain lightweight, have fewer than 7 targets, use private methods and expose a clear public API.

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:22:07.608Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:07.608Z
Learning: Applies to {app/javascript/controllers/**/*.{js,ts},app/components/**/*.{js,ts}} : Keep Stimulus controllers lightweight: aim for fewer than 7 targets; use private methods and expose a clear public API

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:22:00.149Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:00.149Z
Learning: Applies to **/*_controller.{js,ts} : Keep Stimulus controllers focused on UI behavior; avoid domain/business logic and prefer single, closely related responsibilities; use Stimulus callbacks, actions, targets, values, and classes

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
📚 Learning: 2025-09-23T22:22:00.149Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/stimulus_conventions.mdc:0-0
Timestamp: 2025-09-23T22:22:00.149Z
Learning: Applies to app/{views,components}/**/*.html.erb : In ERB views, wire Stimulus interactions declaratively using data-action to call controller methods

Applied to files:

  • app/javascript/controllers/auto_relink_controller.js
  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Pass data from Rails to Stimulus using data-*-value attributes, not inline JavaScript

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/javascript/controllers/**/*.js : Pass data via `data-*-value` attributes, not with inline JavaScript.

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Prefer native HTML elements (e.g., <dialog>, <details><summary>) over JS-based components

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/javascript/controllers,app/components}/**/*.{js,ts} : Use Stimulus targets for DOM access instead of manual document.getElementById calls

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated

Applied to files:

  • app/views/accounts/index.html.erb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Document breaking changes in the rules

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Modify existing rules when implementation details have changed

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Update rules after major refactors

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Add new rules when code reviews repeatedly mention the same feedback

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Document migration paths for deprecated or replaced rules

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-23T22:21:39.905Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/self_improve.mdc:0-0
Timestamp: 2025-09-23T22:21:39.905Z
Learning: Applies to .cursor/rules/**/*.mdc : Add new rules when common bugs could be prevented by a rule

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Never propose system commands (e.g., rails server, rails credentials, migrations) in PR suggestions or code comments.

Applied to files:

  • .junie/guidelines.md
📚 Learning: 2025-09-23T22:23:17.441Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/ui-ux-design-guidelines.mdc:0-0
Timestamp: 2025-09-23T22:23:17.441Z
Learning: Applies to {app/views/**,app/helpers/**,app/javascript/controllers/**} : Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling

Applied to files:

  • .junie/guidelines.md
🧬 Code graph analysis (2)
app/helpers/simplefin_items_helper.rb (1)
app/models/simplefin_item/importer.rb (1)
  • stats (103-105)
app/controllers/simplefin_items_controller.rb (6)
app/controllers/accounts_controller.rb (2)
  • sync (30-46)
  • family (78-80)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (2)
  • dedup_simplefin_accounts! (163-226)
  • merge_duplicate_provider_accounts! (230-274)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/models/simplefin_account.rb (1)
  • current_account (16-18)
🪛 GitHub Check: ci / lint
app/helpers/simplefin_items_helper.rb

[failure] 30-30:
Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.


[failure] 30-30:
Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

🪛 LanguageTool
.junie/guidelines.md

[uncategorized] ~39-~39: The official name of this software platform is spelled with a capital “H”.
Context: ...-enforced) - RuboCop is enforced by CI (bin/rubocop -f github) - Before committing/pushing: - Run ...

(GITHUB)


[grammar] ~63-~63: Use a hyphen to join words.
Context: ...t.userandCurrent.familyfor request scoped context (nevercurrent_user/cu...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
.junie/guidelines.md

20-20: Bare URL used

(MD034, no-bare-urls)


20-20: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (5)
.junie/guidelines.md (1)

1-100: Well-structured guidelines document that aligns with PR #267 objectives.

This document effectively consolidates operational rules, CI expectations, and conventions for the project. It includes important pragmatic rules such as prohibitions for assistants (no rails server, no auto-run migrations) and SimpleFin/Plaid conventions directly relevant to the PR objectives (#110, #254). The guidance on skinny controllers, use of Current context, and frontend token usage will help maintain consistency across the codebase as the SimpleFin enhancements are integrated. Clear, actionable, and well-organized.

app/views/accounts/index.html.erb (1)

46-52: LGTM: Follows Stimulus best practices.

The auto-relink flow now uses a declarative Stimulus controller with data attributes, correctly addressing the previous review feedback. The URL is safely passed via data-auto-relink-url-value, and the controller handles the modal/navigation logic.

app/javascript/controllers/auto_relink_controller.js (1)

1-30: LGTM: Well-structured Stimulus controller.

The controller correctly uses the connect() lifecycle to perform one-time modal initialization, includes robust error handling with fallback navigation, and remains lightweight and focused. The implementation follows Stimulus best practices.

app/controllers/simplefin_items_controller.rb (2)

61-61: Replace bare rescue clauses with explicit exception classes.

Multiple bare rescue clauses throughout the controller catch all exceptions including SystemExit and SignalException. Specify StandardError or the specific exception types expected (e.g., Provider::Simplefin::SimplefinError, ArgumentError, StandardError).

Example for line 61:

-      rescue Provider::Simplefin::SimplefinError, ArgumentError, StandardError => e
+      rescue Provider::Simplefin::SimplefinError, ArgumentError => e

And for lines 73, 145 (currently bare rescue):

-      rescue => e
+      rescue StandardError => e

Also applies to: 74-74, 131-131, 146-146

⛔ Skipped due to learnings
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/provider/**/*.rb : Concrete provider classes must be under the Provider:: namespace, inherit from Provider, wrap calls with with_provider_response, and raise on invalid/unavailable data

351-397: Test coverage for the FK constraint fix with holdings is incomplete.

The test "apply_relink links pairs in one pass and avoids duplicates" creates and verifies the flow with duplicate accounts and the AccountProvider reassignment, but it does not create or assert holdings behavior. No holdings are created or tested in the apply_relink test suite.

The schema confirms the FK constraint exists (add_foreign_key "holdings", "account_providers"), and the controller code correctly updates holdings with account_provider_id before any deletion (line 378 before line 390). However, this critical path—holdings referencing the reused AccountProvider—is not verified by integration tests.

To confirm the fix resolves the PG::ForeignKeyViolation, you should either:

  • Add integration test coverage creating holdings on the provider-linked account before relink, then assert they are successfully reassigned to the manual account with the correct account_provider_id, or
  • Manually verify the relink flow works when holdings exist on the duplicate provider-linked account.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/helpers/simplefin_items_helper.rb (1)

30-30: Fix RuboCop spacing violation.

This issue was already flagged in a previous review. Please add spaces inside the array brackets.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd3f32 and e84ea59.

📒 Files selected for processing (1)
  • app/helpers/simplefin_items_helper.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/helpers/simplefin_items_helper.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/helpers/simplefin_items_helper.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/helpers/simplefin_items_helper.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/helpers/simplefin_items_helper.rb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/helpers/simplefin_items_helper.rb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/helpers/simplefin_items_helper.rb
🧠 Learnings (2)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to **/*.rb : Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Applied to files:

  • app/helpers/simplefin_items_helper.rb
🧬 Code graph analysis (1)
app/helpers/simplefin_items_helper.rb (1)
app/models/simplefin_item/importer.rb (1)
  • stats (103-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: ci / test
🔇 Additional comments (3)
app/helpers/simplefin_items_helper.rb (3)

1-4: LGTM!

Module declaration follows Rails and Ruby conventions correctly.


5-14: LGTM!

Method documentation is clear and includes a helpful example structure.


15-23: LGTM!

Input validation and sample-building logic are sound. The defensive programming with Array() wrapper and dual key support (symbol/string) handles various input formats safely.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (4)
lib/tasks/simplefin_backfill.rake (3)

81-175: Don't call find_each on Arrays.

When selecting by account_id, sfas becomes an Array and .find_each will raise NoMethodError, blocking the task. Branch on the capability before iterating.

-      sfas.find_each do |sfa|
+      iterable = sfas.respond_to?(:find_each) ? sfas : Array.wrap(sfas)
+      iterable.each do |sfa|

75-177: Per-account log output must use per-account counters.

The per-SFA summary prints {seen, matched, updated, skipped, errors} using the global totals, so every row repeats cumulative numbers and hides per-account performance. Track per-account counters alongside the totals before logging.


150-167: Guard external_id and reuse the processor.

If t[:id] is blank you generate "simplefin_", leading to bad lookups, and you instantiate SimplefinEntry::Processor twice. Guard the ID and reuse a single processor instance.

-            adapter = Account::ProviderImportAdapter.new(acct)
-            external_id = "simplefin_#{t[:id]}"
+            adapter = Account::ProviderImportAdapter.new(acct)
+            processor = SimplefinEntry::Processor.new(t, simplefin_account: sfa)
+            external_id = t[:id].present? ? "simplefin_#{t[:id]}" : nil

Then reuse processor for name/amount (dry-run) and process.

app/models/account/provider_import_adapter.rb (1)

150-152: Remove the amount tolerance as agreed.

The ±$0.01 tolerance contradicts the comment on lines 35-36 ("no tolerance, no window") and the agreement to remove it. Since composite_match already uses exact SQL amount matching, this tolerance check is redundant. It should use exact equality to prevent future confusion or misuse.

Apply this diff:

 def within_amount_tolerance?(a, b)
-  (BigDecimal(a.to_s) - BigDecimal(b.to_s)).abs <= BigDecimal("0.01")
+  BigDecimal(a.to_s) == BigDecimal(b.to_s)
 end
🧹 Nitpick comments (4)
app/controllers/simplefin_items_controller.rb (2)

336-339: Potential N+1 queries in relink loop.

Lines 337-338 execute find_by queries for each pair. If processing many relinks, this results in N+1 queries.

Consider eager loading:

+    sfa_ids = pairs.map { |p| p[:sfa_id] }.compact
+    manual_ids = pairs.map { |p| p[:manual_id] }.compact
+    sfas_by_id = @simplefin_item.simplefin_accounts.where(id: sfa_ids).index_by(&:id)
+    manuals_by_id = Current.family.accounts.where(id: manual_ids).index_by(&:id)
+
     SimplefinItem.transaction do
       pairs.each do |pair|
-        sfa = @simplefin_item.simplefin_accounts.find_by(id: pair[:sfa_id])
-        manual = Current.family.accounts.find_by(id: pair[:manual_id])
+        sfa = sfas_by_id[pair[:sfa_id]]
+        manual = manuals_by_id[pair[:manual_id]]
         next unless sfa && manual

474-568: Approve relink candidate heuristics with performance note.

The compute_relink_candidates logic correctly implements conservative matching:

  • Last4 matching with ambiguity guards (Lines 502-531)
  • Balance proximity matching within ±$0.01 (Lines 534-547)
  • Exact normalized name matching (Lines 550-556)
  • Conflict detection prevents incorrect matches (Lines 518-525)

The O(n*m) complexity is acceptable for typical family account counts. For families with hundreds of accounts, consider adding a limit or pagination.

app/models/account/provider_import_adapter.rb (1)

165-173: Consider removing unused normalize_name helpers.

The NAME_STRIP_REGEX constant and normalize_name method appear unused since composite_match was tightened to exact matching without name heuristics. Removing unused code improves maintainability.

app/models/simplefin_account/processor.rb (1)

30-36: Optional: Consider removing redundant guard.

This check is redundant with the early return on line 14. However, keeping it as defensive programming is acceptable and the error message provides useful context if this path is ever reached.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e84ea59 and 1158e47.

📒 Files selected for processing (5)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/models/account/provider_import_adapter.rb (2 hunks)
  • app/models/simplefin_account/processor.rb (1 hunks)
  • config/routes.rb (1 hunks)
  • lib/tasks/simplefin_backfill.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/routes.rb
🧰 Additional context used
📓 Path-based instructions (6)
app/models/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods

Use ActiveRecord validations for forms and complex domain constraints.

app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required

Files:

  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
🧠 Learnings (15)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:40:26.913Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{entry,valuation,transaction,trade}.rb : Entry amounts are signed: negative = inflow, positive = outflow (applies to Entry and its types: Valuation, Transaction, Trade)

Applied to files:

  • app/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (3)
app/models/account/provider_import_adapter.rb (4)
app/models/simplefin_entry/processor.rb (10)
  • external_id (55-59)
  • amount (78-95)
  • currency (97-99)
  • date (105-120)
  • name (61-76)
  • merchant (169-182)
  • notes (189-201)
  • posted_date (122-139)
  • transacted_date (141-158)
  • account (47-49)
app/models/plaid_entry/processor.rb (7)
  • external_id (33-35)
  • amount (41-43)
  • currency (45-47)
  • date (49-51)
  • name (37-39)
  • merchant (62-70)
  • account (29-31)
app/models/plaid_account/investments/transactions_processor.rb (1)
  • account (26-28)
app/models/concerns/enrichable.rb (1)
  • enrich_attribute (26-28)
app/models/simplefin_account/processor.rb (2)
app/models/simplefin_account.rb (2)
  • current_account (16-18)
  • ensure_account_provider! (22-34)
app/models/plaid_account/processor.rb (1)
  • process_account! (31-95)
app/controllers/simplefin_items_controller.rb (6)
app/controllers/accounts_controller.rb (2)
  • sync (30-46)
  • family (78-80)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (2)
  • dedup_simplefin_accounts! (163-226)
  • merge_duplicate_provider_accounts! (230-274)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/models/simplefin_account.rb (1)
  • current_account (16-18)
🔇 Additional comments (17)
app/controllers/simplefin_items_controller.rb (9)

1-1: LGTM!

The Set require is correctly placed and used in compute_relink_candidates for efficient membership tracking.


54-90: Approve enhanced update flow with balances-only sync and relink detection.

The update flow correctly:

  • Runs balances-only discovery to refresh SimpleFin accounts
  • Deduplicates accounts to prevent UI issues
  • Computes unlinked counts for setup CTA control
  • Auto-detects relink candidates and redirects appropriately
  • Handles errors gracefully with rescue blocks and logging

179-188: LGTM!

The destroy action correctly unlinks all provider relationships before deletion, preventing foreign key violations. Error handling is appropriate.


213-229: LGTM!

The setup flow correctly deduplicates accounts before presentation and uses eager loading to avoid N+1 queries. Logging provides good observability.


299-315: LGTM!

The errors action correctly fetches sync stats and normalizes error data for the modal view. Authorization is enforced via set_simplefin_item using Current.family.


317-327: LGTM!

Both relink actions are straightforward and render modal-friendly views. The duplication is intentional for clarity of different flows.


354-404: Approve the apply_relink data migration logic.

The relink logic correctly:

  • Creates or updates the AccountProvider record before moving data (Lines 359-361)
  • Moves entries with duplicate detection on (external_id, source) (Lines 365-374)
  • Moves holdings with duplicate detection and repoints account_provider_id (Lines 377-387)
  • Destroys the redundant provider-linked account after migration (Line 396)
  • Handles the no-existing-account case (Lines 397-403)

This addresses the foreign key constraint issue from past reviews by reusing the provider row.


461-472: LGTM!

Both compute_unlinked_count and normalize_name are well-implemented helpers that reduce duplication and improve readability.


570-576: Approve defensive authorization check in set_simplefin_item.

The method correctly scopes by Current.family when available and falls back to direct lookup, ensuring proper authorization.

app/models/account/provider_import_adapter.rb (6)

18-20: LGTM: Expanded signature supports richer transaction metadata.

The new parameters (fitid, notes, posted_date, transacted_date, pending, extra) enable FITID fallback matching, pending→posted merge detection, and provider metadata persistence as described in the PR objectives.


25-48: LGTM: Three-stage matching flow is well-structured.

The matching priority (exact external_id+source → FITID fallback → composite_match) prevents duplicates while supporting legacy FITID identifiers. Updating external_id on line 46 ensures future syncs use provider IDs, and the was_merged flag correctly captures pending→posted progressions.


76-79: LGTM: Notes enrichment follows the established pattern.

Using enrich_attribute respects user overrides while allowing provider-sourced notes to populate empty fields.


80-86: LGTM: Extra metadata persistence is correctly implemented.

The non-enriched, always-merged approach for extra metadata is appropriate for accumulating provider-specific data. Using deep_merge handles nested structures correctly.


87-94: LGTM: Single-record merge detection handles provider variations.

This correctly handles providers that send a single transaction with both transacted_date and posted_date instead of separate pending/posted records, ensuring the "Merged" badge displays appropriately.


100-107: LGTM: Composite matching uses strict exact matching.

The SQL query correctly implements exact date and exact amount matching as documented in the comment, preventing false positives from loose heuristics.

app/models/simplefin_account/processor.rb (2)

12-14: LGTM: Auto-linking correctly removed.

The early return when current_account is missing prevents auto-linking and correctly implements the manual relink flow as agreed in previous reviews. The explicit comment reinforces the intent.


17-22: LGTM: Provider link validation is appropriately positioned.

Calling ensure_account_provider! after account processing establishes the AccountProvider relationship for multi-provider tracking. The error handling logs failures without halting the sync, which is appropriate for this non-critical step.

luckyPipewrench pushed a commit to luckyPipewrench/sure that referenced this pull request Oct 31, 2025
…2). Address all 15 actionable CodeRabbit comments: Add UUID validations in rakes (e.g., simplefin_unlink), swap Ruby pattern matching/loops for efficient DB queries (e.g., where LOWER(name) LIKE ?), generate docstrings for low-coverage areas (31%), consolidate routes for simplefin_items, move view logic to helpers (e.g., format_transaction_extra), strengthen tests with exact assertions/fixtures for dedup/relink failures. Also, check for overlaps with merged we-promise#262 (merchants fix): Ensure merchant creation in simplefin_entry/processor.rb aligns with new payee-based flow and MD5 IDs; add tests for edge cases like empty payees or over-merging pendings. Prioritize security (PII redaction in logs, no hardcoded secrets).
@luckyPipewrench luckyPipewrench force-pushed the simplefin-enhancements-v2 branch from 1158e47 to e707869 Compare October 31, 2025 14:39
@luckyPipewrench
Copy link
Collaborator Author

Rebased on main

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/models/family/auto_transfer_matchable.rb (1)

63-76: Post-rescue logic executes even when transfer creation was skipped.

When RecordNotUnique is rescued (line 68), it means another concurrent process created the transfer. However, lines 72-76 still execute, causing:

  • Redundant transaction updates that may have already been applied by the concurrent process
  • Incorrect tracking in used_transaction_ids for transfers not created by this process

Consider using create_or_find_by (Rails 6+) or checking if the transfer is newly created:

-        begin
-          Transfer.find_or_create_by!(
-            inflow_transaction_id: match.inflow_transaction_id,
-            outflow_transaction_id: match.outflow_transaction_id,
-          )
-        rescue ActiveRecord::RecordNotUnique
-          # Another concurrent job created the transfer; safe to ignore
-        end
-
-        Transaction.find(match.inflow_transaction_id).update!(kind: "funds_movement")
-        Transaction.find(match.outflow_transaction_id).update!(kind: Transfer.kind_for_account(Transaction.find(match.outflow_transaction_id).entry.account))
-
-        used_transaction_ids << match.inflow_transaction_id
-        used_transaction_ids << match.outflow_transaction_id
+        transfer = Transfer.create_or_find_by(
+          inflow_transaction_id: match.inflow_transaction_id,
+          outflow_transaction_id: match.outflow_transaction_id
+        )
+
+        # Only update transactions if this process created the transfer
+        if transfer.previously_new_record?
+          Transaction.find(match.inflow_transaction_id).update!(kind: "funds_movement")
+          Transaction.find(match.outflow_transaction_id).update!(kind: Transfer.kind_for_account(Transaction.find(match.outflow_transaction_id).entry.account))
+
+          used_transaction_ids << match.inflow_transaction_id
+          used_transaction_ids << match.outflow_transaction_id
+        end
app/models/simplefin_entry/processor.rb (1)

10-25: Remove unsupported parameters from import_transaction call.

The process method passes notes, posted_date, transacted_date, pending, and extra to import_transaction, but the method signature only accepts external_id, amount, currency, date, name, source, category_id, and merchant. This will raise ArgumentError: unknown keyword at runtime. Either remove these parameters from the call or add support for them in Account::ProviderImportAdapter#import_transaction (app/models/account/provider_import_adapter.rb:19).

♻️ Duplicate comments (13)
lib/tasks/holdings_tools.rake (1)

26-31: Restore the dry-run default to prevent accidental writes.

This issue was already flagged in a previous review. The current logic treats the absence of dry_run as false, causing unintended writes when users expect preview-only behavior. The task description and examples indicate dry-run should be the default.

app/models/simplefin_account.rb (1)

26-33: Fix provider reassignment to avoid unique violations

If a SimpleFIN item is re-linked to a different Sure account, the existing AccountProvider row (scoped by provider_type/provider_id) already exists with the old account. Because the find_or_create_by! here scopes on account: acct, we fail to find it and attempt to insert a new row, tripping the unique index on provider_type/provider_id and leaving the stale link behind. Please reuse the existing provider row and reassign its account before saving, as previously suggested.

-    AccountProvider.find_or_create_by!(
-      account: acct,
-      provider_type: "SimplefinAccount",
-      provider_id: id
-    )
+    AccountProvider
+      .find_or_initialize_by(provider_type: "SimplefinAccount", provider_id: id)
+      .tap do |provider|
+        provider.account = acct
+        provider.save!
+      end
db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1)

3-3: Align migration version with Rails 7.2

Please bump this to ActiveRecord::Migration[7.2] to match the app’s current Rails level so new migrations pick up the right framework defaults.(guides.rubyonrails.org)

lib/tasks/simplefin_debug.rake (1)

1-55: Fix timestamp coercion for sorting and pending inference.

ISO 8601 strings like "2025-10-31" still flow through posted.to_i / trans.to_i, which collapses them to 2025. That causes the sample list to sort almost randomly and marks transactions as “pending” when they aren’t. Please coerce to epoch seconds (handling numerics, digit strings, and ISO timestamps) before both sort and comparison; this was already flagged earlier and the bug is still present.

+# frozen_string_literal: true
+
+require "json"
+require "time"
...
-      txs = Array(sfa.raw_transactions_payload)
-      # Sort by best-known date: posted -> transacted_at -> as-is
-      txs = txs.map { |t| t.with_indifferent_access }
-      txs.sort_by! do |t|
-        posted = t[:posted]
-        trans = t[:transacted_at]
-        ts = if posted.is_a?(Numeric)
-          posted
-        elsif trans.is_a?(Numeric)
-          trans
-        else
-          0
-        end
-        -ts
-      end
+      txs = sfa.raw_transactions_payload
+      txs = JSON.parse(txs) if txs.is_a?(String)
+      txs = Array.wrap(txs).map { |t| t.with_indifferent_access }
+
+      coerce_ts = ->(value) do
+        case value
+        when Numeric then value.to_i
+        when String
+          stripped = value.strip
+          return stripped.to_i if stripped.match?(/\A\d+\z/)
+          Time.parse(stripped).to_i
+        else
+          0
+        end
+      rescue ArgumentError
+        0
+      end
+
+      txs.sort_by! do |t|
+        best = coerce_ts.call(t[:posted])
+        best = coerce_ts.call(t[:transacted_at]) if best.zero?
+        -best
+      end
...
-          inferred_pending: (trans.present? && posted.present? && posted.to_i > trans.to_i)
+          inferred_pending: (coerce_ts.call(trans).positive? && coerce_ts.call(posted) > coerce_ts.call(trans))
db/schema.rb (1)

28-30: Drop the duplicate SimpleFin provider unique index.

account_providers now has both index_account_providers_on_account_and_provider_type and index_account_providers_on_account_id_and_provider_type covering the same column set. That doubles uniqueness checks and wastes storage without increasing safety. Please update the migration to drop/rename the legacy index instead of layering a second copy, then regenerate the schema so only one remains.

lib/tasks/was_merged.rake (1)

18-18: Fix dynamic constant assignment

Ruby forbids defining constants inside method bodies. This issue was previously flagged and remains unresolved.

lib/tasks/simplefin_backfill.rake (4)

81-81: Blocker: find_each will crash when sfas is an Array.

On line 69, sfas can become Array.wrap(sfa), which is a plain Array. Calling .find_each on an Array raises NoMethodError. This issue was previously flagged and remains unresolved.

Apply this fix:

-      sfas.find_each do |sfa|
+      Array(sfas).each do |sfa|

75-80: Per-SFA summary uses cumulative totals instead of per-SFA counters.

Line 174 prints { seen: total_seen, matched: total_matched, ... } for each SFA, but these are global counters that accumulate across all SFAs. This misleads operators reviewing per-account stats. This issue was previously flagged and remains unresolved.

Initialize per-SFA counters inside the loop at line 81 (e.g., s_seen = s_matched = s_updated = s_skipped = s_errors = 0) and increment both the per-SFA and global counters throughout the processing, then use the per-SFA counters in the output at line 174.

Also applies to: 174-174


152-152: Guard external_id when missing and avoid duplicate Processor instantiation.

Line 152 builds external_id = "simplefin_#{t[:id]}" without checking if t[:id] is present, resulting in "simplefin_" when nil. Line 157 instantiates SimplefinEntry::Processor twice for the same transaction. Both issues were previously flagged and remain unresolved.

Guard against missing t[:id] and instantiate the processor once:

             adapter = Account::ProviderImportAdapter.new(acct)
-            external_id = "simplefin_#{t[:id]}"
+            external_id = t[:id].present? ? "simplefin_#{t[:id]}" : nil

             if dry_run
               # Simulate: check if we can composite-match; we won't persist
-              entry = acct.entries.find_by(external_id: external_id, source: "simplefin")
-              entry ||= adapter.composite_match(source: "simplefin", name: SimplefinEntry::Processor.new(t, simplefin_account: sfa).send(:name), amount: SimplefinEntry::Processor.new(t, simplefin_account: sfa).send(:amount), date: (posted_d || trans_d))
+              entry = external_id && acct.entries.find_by(external_id: external_id, source: "simplefin")
+              processor = SimplefinEntry::Processor.new(t, simplefin_account: sfa)
+              entry ||= adapter.composite_match(
+                source: "simplefin",
+                name: processor.send(:name),
+                amount: processor.send(:amount),
+                date: (posted_d || trans_d)
+              )

Also applies to: 157-157


193-193: Fix cleanup task default: it deletes by default.

Line 193 computes dry_run from a truthy check but does NOT default to true when blank. Running sure:simplefin:cleanup_demo_entries without args will delete entries immediately, contrary to operator expectations and the pattern in backfill_extra. This issue was previously flagged and remains unresolved.

Apply this diff to default to true when blank:

       account_id = (kv["account_id"] || args[:account_id]).presence
       dry_raw     = (kv["dry_run"] || args[:dry_run]).to_s.downcase
       pattern     = (kv["pattern"] || args[:pattern]).presence || "simplefin_posted_demo_%|simplefin_posted_ui"

-      dry_run = %w[1 true yes y].include?(dry_raw)
+      dry_run = dry_raw.blank? ? true : %w[1 true yes y].include?(dry_raw)
app/controllers/simplefin_items_controller.rb (3)

136-147: Refactor to use the compute_unlinked_count helper for consistency.

Lines 136-139 duplicate the logic already extracted in compute_unlinked_count (line 461). The update action uses the helper (line 67), but create doesn't. This issue was previously flagged and remains unresolved.

Apply this diff:

       # Recompute unlinked count and clear pending flag when zero
       begin
-        unlinked = @simplefin_item.simplefin_accounts
-          .left_joins(:account, :account_provider)
-          .where(accounts: { id: nil }, account_providers: { id: nil })
-          .count
+        unlinked = compute_unlinked_count(@simplefin_item)
         Rails.logger.info("SimpleFin create: unlinked_count=#{unlinked} (controls setup CTA) for item_id=#{@simplefin_item.id}")

202-209: Move synchronous SimpleFin API call to a background job.

Line 204 calls perform_sync synchronously in the request thread. If the SimpleFin API is slow or unavailable, this blocks the request and degrades user experience. This issue was previously flagged and remains unresolved.

Consider using an async pattern similar to the sync action:

   def balances
+    unless @simplefin_item.syncing?
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end

     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

428-439: Refactor to use the compute_unlinked_count helper.

Lines 428-431 duplicate the same query logic found in the create action (lines 136-139) and extracted in the compute_unlinked_count helper (line 461). This issue was previously flagged and remains unresolved.

Apply this diff:

       # Recompute unlinked count and clear pending flag when zero
       begin
-        unlinked = @simplefin_item.simplefin_accounts
-          .left_joins(:account, :account_provider)
-          .where(accounts: { id: nil }, account_providers: { id: nil })
-          .count
+        unlinked = compute_unlinked_count(@simplefin_item)
         Rails.logger.info("SimpleFin apply_relink: unlinked_count=#{unlinked} (controls setup CTA) for item_id=#{@simplefin_item.id}")
🧹 Nitpick comments (2)
app/models/family/auto_transfer_matchable.rb (1)

72-73: Consider optimizing N+1 queries.

The Transaction.find calls execute individually for each match within the loop, potentially causing N+1 queries. Consider batch-loading transactions upfront or using update_all where appropriate for better performance.

Example optimization:

# Before the loop, collect all transaction IDs
transaction_ids = candidates_scope.pluck(:inflow_transaction_id, :outflow_transaction_id).flatten.uniq
transactions_by_id = Transaction.where(id: transaction_ids).includes(entry: :account).index_by(&:id)

# Inside the loop, use the preloaded hash
inflow_txn = transactions_by_id[match.inflow_transaction_id]
outflow_txn = transactions_by_id[match.outflow_transaction_id]

inflow_txn.update!(kind: "funds_movement")
outflow_txn.update!(kind: Transfer.kind_for_account(outflow_txn.entry.account))
app/views/transactions/show.html.erb (1)

102-145: Localize the new drawer label and strings.

Everything else in this view pulls copy from t(".…"), but the new “Additional details”, “Provider extras”, and field labels are hard-coded. Please move them into the locale file so the UI stays translatable and consistent.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (3)
app/views/simplefin_items/_simplefin_item.html.erb (3)

45-45: Replace bare rescue with explicit exception class.

Bare rescue catches all exceptions including SystemExit and SignalException. Use rescue StandardError for safer error handling.


198-198: Replace bare rescue with explicit exception class.

Same issue as line 45: bare rescue should be rescue StandardError.


214-217: Move business logic out of the view.

Computing unlinked_count directly in the ERB template violates the guideline to keep domain logic out of templates. This query logic should be computed in the controller or a helper method and passed as a local variable.

🧹 Nitpick comments (2)
app/models/account/provider_import_adapter.rb (2)

66-68: Remove inconsistent respond_to? check.

The respond_to?(:enrich_attribute) check is inconsistent with the enrichment pattern used elsewhere in this method (lines 55, 59, 63). If entry doesn't respond to enrich_attribute, those other calls would also fail. This defensive check adds unnecessary complexity without clear benefit.

Apply this diff to align with the existing pattern:

-if notes.present? && entry.respond_to?(:enrich_attribute)
+if notes.present?
   entry.enrich_attribute(:notes, notes, source: source)
 end

70-75: Verify the non-enriched deep merge strategy for extra metadata.

The extra field uses deep_merge and explicitly bypasses the enrichment pattern (comment: "non-enriched; always merged"). This means:

  1. User modifications to transaction.extra will be overwritten on subsequent syncs
  2. Partial updates to nested keys in extra will merge recursively, which might not match caller intent
  3. Non-Hash values are silently converted to empty hashes (extra.is_a?(Hash) ? extra.deep_stringify_keys : {})

Confirm this design is intentional. If extra should respect user overrides like other fields, consider adopting the enrichment pattern. If not, document why this field behaves differently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e707869 and 9e5618a.

📒 Files selected for processing (3)
  • app/models/account/provider_import_adapter.rb (2 hunks)
  • app/models/simplefin_entry/processor.rb (3 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/simplefin_entry/processor.rb
🧰 Additional context used
📓 Path-based instructions (16)
app/models/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods

Use ActiveRecord validations for forms and complex domain constraints.

app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required

Files:

  • app/models/account/provider_import_adapter.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/models/account/provider_import_adapter.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/models/account/provider_import_adapter.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/models/account/provider_import_adapter.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/models/account/provider_import_adapter.rb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/models/account/provider_import_adapter.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/_*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
🧠 Learnings (8)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • app/models/account/provider_import_adapter.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{entry,valuation,transaction,trade}.rb : Entry amounts are signed: negative = inflow, positive = outflow (applies to Entry and its types: Valuation, Transaction, Trade)

Applied to files:

  • app/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/models/account/provider_import_adapter.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to app/views/**/*.erb : Keep heavy logic out of ERB views; prefer helpers/components instead

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Keep domain logic out of ERB templates; compute classes and logic in component/Ruby code, not inline in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use partials when content is primarily static, simple, context-specific, and without variants or complex configuration

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
🧬 Code graph analysis (1)
app/models/account/provider_import_adapter.rb (2)
app/models/simplefin_entry/processor.rb (4)
  • date (103-118)
  • notes (187-199)
  • posted_date (120-137)
  • transacted_date (139-156)
app/models/concerns/enrichable.rb (1)
  • enrich_attribute (26-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (10)
app/views/simplefin_items/_simplefin_item.html.erb (4)

154-213: Move latest sync computation to controller to eliminate redundant queries.

Line 155 queries simplefin_item.syncs.ordered.first inline, duplicating logic that already computes latest_sync in the controller (line 16 of accounts_controller.rb). While the includes(:syncs) prevents N+1, this duplication is unnecessary.

Pass @latest_sync and @sync_stats as locals or instance variables and reference them here:

-        <% if (latest_sync = simplefin_item.syncs.ordered.first).present? %>
-          <% stats = latest_sync.sync_stats || {} %>
+        <% latest_sync_for_item = @simplefin_latest_sync_map[simplefin_item.id] %>
+        <% if latest_sync_for_item.present? %>
+          <% stats = latest_sync_for_item.sync_stats || {} %>

Update the controller to also populate @simplefin_latest_sync_map alongside the stats map.

As per coding guidelines.


46-46: [STILL UNFIXED] Replace bare rescue with explicit exception class.

Despite the past review comment, line 46 still uses a bare rescue that catches all exceptions including SystemExit and SignalException. Inline rescues should explicitly catch StandardError.

Apply this diff:

-                  <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue nil) : nil) %>
+                  <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue StandardError nil) : nil) %>

200-200: [STILL UNFIXED] Replace bare rescue with explicit exception class.

Line 200 has the same bare rescue issue as line 46. Use rescue StandardError to avoid catching system-level exceptions.

Apply this diff:

-                    <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue nil) : nil) %>
+                    <% ago = (ts.present? ? (time_ago_in_words(Time.parse(ts)) rescue StandardError nil) : nil) %>

216-219: Move unlinked count computation to controller.

Lines 216-219 execute a database query inline in the view, violating the guideline to keep domain logic out of ERB templates. This same query is already used to populate @simplefin_has_unlinked_map in the controller; extend that logic to also compute the count.

Move this to the controller:

# In controller before rendering:
@simplefin_unlinked_count_map = {}
@simplefin_items.each do |item|
  @simplefin_unlinked_count_map[item.id] = item.simplefin_accounts
    .left_joins(:account, :account_provider)
    .where(accounts: { id: nil }, account_providers: { id: nil })
    .count
end

Then reference @simplefin_unlinked_count_map[simplefin_item.id] in the view.

As per coding guidelines.

app/controllers/simplefin_items_controller.rb (3)

137-140: Use the compute_unlinked_count helper for consistency.

Lines 137-140 duplicate the unlinked count query inline instead of using the compute_unlinked_count helper (from SimplefinItems::RelinkHelpers, line 68 of this file shows it's already used in update).

Apply this diff:

       # Recompute unlinked count and clear pending flag when zero
       begin
-        unlinked = @simplefin_item.simplefin_accounts
-          .left_joins(:account, :account_provider)
-          .where(accounts: { id: nil }, account_providers: { id: nil })
-          .count
+        unlinked = compute_unlinked_count(@simplefin_item)
         Rails.logger.info("SimpleFin create: unlinked_count=#{unlinked} (controls setup CTA) for item_id=#{@simplefin_item.id}")

203-211: Move synchronous sync to background job.

Line 205 calls perform_sync synchronously in the request thread, blocking until the SimpleFin API completes. This is identical to the issue flagged in the past review for lines 203-210.

Use an async pattern:

   def balances
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+    unless @simplefin_item.syncing?
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end

     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

122-133: Move synchronous balances-only import to background job.

Lines 123-133 perform a synchronous import_balances_only call during the HTTP request, similar to the issue in the update action (lines 56-64). This blocks the user and consumes a request thread while waiting for the SimpleFin API.

Apply the same async pattern suggested for the update action, or document why synchronous execution is acceptable if this is an intentional lightweight operation.

lib/tasks/holdings_tools.rake (1)

26-31: [STILL UNFIXED] Dry-run defaults to destructive writes.

Despite the past review comment being marked as addressed, the current code still treats absent dry_run arguments as false, causing the task to persist snapshots by default. When both kv["dry_run"] and args[:dry_run] are nil, line 29 produces "", and line 30 evaluates to false, enabling writes without explicit user consent.

Apply this diff to restore safe preview-by-default behavior:

       holding_id = (kv["holding_id"] || args[:holding_id]).presence
       change_pct = ((kv["change_pct"] || args[:change_pct] || 2).to_f) / 100.0
       days_ago   = (kv["days_ago"] || args[:days_ago] || 1).to_i
       dry_raw    = (kv["dry_run"] || args[:dry_run]).to_s.downcase
-      dry_run    = %w[1 true yes y].include?(dry_raw)
+      dry_run    = dry_raw.blank? ? true : %w[1 true yes y].include?(dry_raw)
lib/tasks/simplefin_fix_was_merged.rake (2)

72-124: [CRITICAL] Transaction boundary still excludes recompute phase.

Despite the past review comment being marked as addressed, the clear phase (lines 73-85) and recompute phase (lines 101-120) remain in separate transactions. If the recompute phase fails after the clear transaction commits, was_merged flags are permanently lost without restoration, leaving the database in an inconsistent state.

Wrap both phases in a single transaction when recompute is enabled:

       unless dry_run
-        ActiveRecord::Base.transaction do
+        ActiveRecord::Base.transaction do
           # Clear flags first
           entries_scope.includes(:entryable).find_each do |e|
             t = e.entryable
             next unless t.is_a?(Transaction)
             next unless t.respond_to?(:was_merged)

             if t.was_merged
               t.update!(was_merged: false)
               cleared += 1
             end
           end
-        end

-        if recompute
+          if recompute
             # Try to reprocess SimpleFin raw payload for the linked SimplefinAccount
             sfa = begin
               # Prefer AccountProvider linkage
               ap = acct.account_providers.where(provider_type: "SimplefinAccount").first
               ap&.provider
             rescue
               nil
             end

             sfa ||= SimplefinAccount.find_by(account: acct)

             if sfa && sfa.raw_transactions_payload.present?
               txs = Array(sfa.raw_transactions_payload).map { |t| t.with_indifferent_access }
-              ActiveRecord::Base.transaction do
                 txs.each do |t|
                   begin
                     posted_d = Simplefin::DateUtils.parse_provider_date(t[:posted])
                     trans_d  = Simplefin::DateUtils.parse_provider_date(t[:transacted_at])
                     best = posted_d || trans_d
                     next if best.nil? || best < window_start || best > window_end

                     # Re-run processor (idempotent); current heuristics will set was_merged where appropriate
                     SimplefinEntry::Processor.new(t, simplefin_account: sfa).process
                     updated += 1
                   rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound => e
                     errors += 1
                     puts({ warn: "recompute_error", message: e.message, tx_id: t[:id] }.to_json)
                   rescue ArgumentError, TypeError => e
                     errors += 1
                     puts({ warn: "recompute_parse_error", message: e.message, tx_id: t[:id] }.to_json)
                   end
                 end
-              end
             else
               puts({ info: "no_raw_transactions", message: "Unable to recompute without raw SimpleFin payload" }.to_json)
             end
+          end
-        end
+        end
       end

89-95: [STILL UNFIXED] Replace bare rescue with explicit exception class.

Despite being marked as addressed in past commits, line 93 still uses a bare rescue that catches all exceptions, including system-level signals. While the intent is to fallback to nil when provider linkage fails, the rescue should explicitly catch StandardError to avoid suppressing critical system exceptions.

Apply this diff:

           sfa = begin
             # Prefer AccountProvider linkage
             ap = acct.account_providers.where(provider_type: "SimplefinAccount").first
             ap&.provider
-          rescue
+          rescue StandardError
             nil
           end
🧹 Nitpick comments (2)
app/controllers/accounts_controller.rb (1)

15-22: Optimize per-item unlinked query to reduce redundant database calls.

Lines 18-21 execute the same family.accounts.left_joins(:account_providers)... query once for each simplefin_item, even though all items share the same family. Since the family context is constant, computing has_unlinked_accounts once before the loop (or restructuring the query to compute per-item status in bulk) would eliminate redundant queries.

Consider computing the unlinked status outside the loop:

     @simplefin_sync_stats_map = {}
     @simplefin_has_unlinked_map = {}
+    
+    # Compute once: does this family have any unlinked accounts?
+    family_has_unlinked = family.accounts
+      .left_joins(:account_providers)
+      .where(account_providers: { id: nil })
+      .exists?

     @simplefin_items.each do |item|
       latest_sync = item.syncs.ordered.first
       @simplefin_sync_stats_map[item.id] = (latest_sync&.sync_stats || {})
-      @simplefin_has_unlinked_map[item.id] = item.family.accounts
-        .left_joins(:account_providers)
-        .where(account_providers: { id: nil })
-        .exists?
+      @simplefin_has_unlinked_map[item.id] = family_has_unlinked
     end

Alternatively, if per-item granularity is required (checking which items have unlinked accounts under their scope), clarify the intent with a comment.

lib/simplefin/date_utils.rb (1)

7-26: LGTM—robust date parsing utility.

The implementation correctly handles multiple provider date formats (String, Numeric epoch, Time/DateTime, Date) with appropriate error handling. The rescue clause properly catches ArgumentError and TypeError, returning nil for unparseable inputs.

However, note that SimplefinEntry::Processor (lines 117-153) duplicates similar date-parsing logic inline. Consider refactoring the processor to use this utility for consistency:

def posted_date
  Simplefin::DateUtils.parse_provider_date(data[:posted])
end

def transacted_date
  Simplefin::DateUtils.parse_provider_date(data[:transacted_at])
end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0326fd3 and 0043755.

📒 Files selected for processing (9)
  • app/controllers/accounts_controller.rb (1 hunks)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/models/account/provider_import_adapter.rb (2 hunks)
  • app/models/simplefin_entry/processor.rb (3 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (3 hunks)
  • lib/simplefin/date_utils.rb (1 hunks)
  • lib/tasks/holdings_tools.rake (1 hunks)
  • lib/tasks/simplefin_fix_was_merged.rake (1 hunks)
  • lib/tasks/simplefin_unlink.rake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/tasks/simplefin_unlink.rake
  • app/models/account/provider_import_adapter.rb
🧰 Additional context used
📓 Path-based instructions (16)
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/models/simplefin_entry/processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/accounts_controller.rb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/views/**/_*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Files:

  • app/views/simplefin_items/_simplefin_item.html.erb
app/models/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods

Use ActiveRecord validations for forms and complex domain constraints.

app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required

Files:

  • app/models/simplefin_entry/processor.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/models/simplefin_entry/processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/accounts_controller.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/models/simplefin_entry/processor.rb
  • app/controllers/simplefin_items_controller.rb
  • lib/simplefin/date_utils.rb
  • app/controllers/accounts_controller.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/models/simplefin_entry/processor.rb
  • app/controllers/simplefin_items_controller.rb
  • lib/simplefin/date_utils.rb
  • app/controllers/accounts_controller.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/models/simplefin_entry/processor.rb
🧠 Learnings (20)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator

Applied to files:

  • lib/tasks/holdings_tools.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/models/simplefin_entry/processor.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/accounts_controller.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Make changes atomic, testable, and explain their impact briefly in code suggestions.

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • lib/tasks/simplefin_fix_was_merged.rake
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to app/views/**/*.erb : Keep heavy logic out of ERB views; prefer helpers/components instead

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Keep domain logic out of ERB templates; compute classes and logic in component/Ruby code, not inline in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use partials when content is primarily static, simple, context-specific, and without variants or complex configuration

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/accounts_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/**/*.erb : Be mindful of performance in global layouts (e.g., avoid loading large data payloads)

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/controllers/accounts_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/accounts_controller.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/components/**/* : Logic belongs in component files, not in component template (*.html.erb, *.html.slim) files.

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/models/simplefin_entry/processor.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
  • app/controllers/accounts_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (3)
app/models/simplefin_entry/processor.rb (1)
app/models/simplefin_account/transactions/processor.rb (1)
  • account (39-41)
app/controllers/simplefin_items_controller.rb (7)
app/models/simplefin_item.rb (3)
  • include (1-301)
  • dedup_simplefin_accounts! (163-226)
  • merge_duplicate_provider_accounts! (230-274)
app/models/simplefin_item/relink_service.rb (2)
  • new (3-122)
  • apply! (14-121)
app/models/simplefin_item/provided.rb (1)
  • simplefin_provider (4-6)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
  • compute_unlinked_count (10-15)
  • compute_relink_candidates (23-114)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/accounts_controller.rb (1)
app/models/concerns/syncable.rb (1)
  • latest_sync (67-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (10)
app/views/simplefin_items/_simplefin_item.html.erb (2)

30-68: Good use of precomputed stats to avoid N+1 queries.

Lines 31-32 correctly reference @simplefin_sync_stats_map[simplefin_item.id] precomputed in the controller (see app/controllers/accounts_controller.rb lines 12-17), eliminating the N+1 query concern from the past review. The fallback to defined?(sync_stats) preserves flexibility for different rendering contexts.


123-133: Good use of precomputed unlinked status.

Line 124 correctly references @simplefin_has_unlinked_map[simplefin_item.id] precomputed in the controller (see app/controllers/accounts_controller.rb lines 13, 18-21), addressing the past concern about executing database queries in the view.

app/models/simplefin_entry/processor.rb (4)

27-36: Well-structured metadata collection.

The extra_metadata method cleanly preserves raw provider data under a namespaced "simplefin" key, preventing data loss and supporting future extensibility. The method correctly checks for key presence and type guards extra as a Hash.


96-115: Date selection logic is sound.

The account-type-specific date preference (lines 102-112) appropriately prioritizes transaction dates for credit cards/loans (matching statement logic) while preferring posted dates for other accounts. Raising ArgumentError when both dates are missing (line 114) ensures data integrity.


155-162: Pending inference logic is appropriate.

The method correctly prioritizes an explicit pending flag (line 158) while providing a reasonable fallback heuristic (posted date after transacted date suggests a pending transaction, line 161). The nil-safe checks prevent errors.


184-196: Well-composed notes field.

The notes method provides useful context by including memo and conditionally adding payee information when it differs from description (lines 192-194). The separator and nil-handling (line 195) keep the output clean and concise.

app/controllers/simplefin_items_controller.rb (4)

213-230: Well-structured setup_accounts filtering with observability.

Lines 215-219 safely attempt deduplication with error recovery, and lines 221-224 correctly use includes to eager-load associations before filtering for unlinked accounts, avoiding N+1 queries. The logging (lines 227-229) provides useful observability for the setup flow.


300-316: Clean error modal implementation.

The errors action appropriately fetches the latest sync stats (line 302), normalizes error data for display (lines 306-313), and renders without layout for modal presentation (line 315). The symbol/string key handling (lines 308-311) ensures compatibility with different serialization contexts.


330-352: Well-structured relink application with service delegation.

The apply_relink action correctly delegates business logic to SimplefinItem::RelinkService (lines 335-339), handles multiple response formats (Turbo Stream, HTML, JSON), and provides detailed result data (line 350). The Turbo Stream response properly removes the modal and updates the item card (lines 344-347).


359-362: Appropriate family scoping for security.

Lines 360-361 correctly scope the lookup to Current.family when available, ensuring users can only access SimpleFin items within their family context, while providing a fallback for admin or system contexts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
app/controllers/simplefin_items_controller.rb (2)

119-122: Use the compute_unlinked_count helper for consistency.

This query duplicates the logic already extracted in the compute_unlinked_count helper (defined in SimplefinItems::RelinkHelpers). The update action uses the helper correctly at Line 60.

Apply this diff:

-        unlinked = @simplefin_item.simplefin_accounts
-          .left_joins(:account, :account_provider)
-          .where(accounts: { id: nil }, account_providers: { id: nil })
-          .count
+        unlinked = compute_unlinked_count(@simplefin_item)

186-187: Move synchronous SimpleFin sync to background job.

Line 187 calls perform_sync synchronously in the request thread. If the SimpleFin API is slow or unavailable, this blocks the request and degrades user experience. This is a reliability anti-pattern and was flagged in a previous review.

Consider using an async pattern consistent with the sync action:

   def balances
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+    unless @simplefin_item.syncing?
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end
 
     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

Alternatively, leverage the existing sync_later infrastructure if it can accept a balances-only flag.

🧹 Nitpick comments (1)
app/jobs/simplefin_item/balances_only_job.rb (1)

20-20: Optional: Simplify redundant exception classes.

ArgumentError is redundant since it's already covered by StandardError.

Apply this diff to simplify:

-    rescue Provider::Simplefin::SimplefinError, ArgumentError, StandardError => e
+    rescue Provider::Simplefin::SimplefinError, StandardError => e
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0043755 and 7848530.

📒 Files selected for processing (2)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/jobs/simplefin_item/balances_only_job.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/controllers/simplefin_items_controller.rb
🧠 Learnings (12)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (2)
app/jobs/simplefin_item/balances_only_job.rb (3)
app/models/simplefin_item/provided.rb (1)
  • simplefin_provider (4-6)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (2)
  • dedup_simplefin_accounts! (163-226)
  • merge_duplicate_provider_accounts! (230-274)
app/controllers/simplefin_items_controller.rb (5)
app/models/simplefin_item.rb (2)
  • include (1-301)
  • dedup_simplefin_accounts! (163-226)
app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
  • compute_unlinked_count (10-15)
  • compute_relink_candidates (23-114)
app/models/simplefin_item/relink_service.rb (2)
  • new (3-122)
  • apply! (14-121)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
🔇 Additional comments (11)
app/controllers/simplefin_items_controller.rb (8)

1-4: LGTM!

The additions are appropriate: Set supports the relink candidate tracking, RelinkHelpers centralizes relink logic, and all new actions are correctly listed in the before_action.


55-82: LGTM! Async job pattern and relink flow improve UX.

The switch to SimplefinItem::BalancesOnlyJob.perform_later addresses the past concern about blocking requests. The relink candidate detection and auto-modal flow provide smooth onboarding when matches exist.


162-170: LGTM! Proper unlinking before deletion.

The addition of SimplefinItem::Unlinker ensures provider-linked accounts are safely moved to "Other accounts" before the item is destroyed. Error handling is appropriate.


197-211: LGTM! Deduplication and improved filtering.

The deduplication safeguard prevents duplicate UI rows, and the includes(:account, :account_provider) prevents N+1 queries. The logging provides good observability for setup account filtering.


282-298: LGTM! Clean error listing action.

The action safely extracts per-account errors from the latest sync stats with defensive programming (|| {}, Array()), and the key normalization handles both symbol and string keys gracefully.


300-310: LGTM! Clean relink modal actions.

Both actions appropriately delegate to compute_relink_candidates and render modal-friendly views. The explicit manual_relink endpoint adds clarity to the user flow without significant duplication.


312-334: LGTM! Well-structured apply_relink action.

The action properly filters checked pairs, delegates to SimplefinItem::RelinkService, and returns appropriate responses for multiple formats. The Turbo Stream response correctly removes the modal and updates the SimpleFin card.


341-344: LGTM! Proper scoping with fallback.

The set_simplefin_item method correctly respects the current family context when available and safely falls back to the global scope.

app/jobs/simplefin_item/balances_only_job.rb (3)

1-4: LGTM - Standard ActiveJob setup.

The job class follows Rails conventions with proper frozen string literal pragma and explicit queue assignment.


6-14: LGTM - Defensive item lookup.

The documentation clearly describes the job's operations, and the use of find_by with an early return guard is appropriate for gracefully handling missing records without raising exceptions.


24-41: Code at lines 24-41 is correct—best-effort cleanup is appropriately handled.

The verification confirms that Account#sync_data does not exist in the codebase, and SimpleFin's balances-only job is intentionally designed as a lightweight operation. The best-effort error handling (bare rescue with logging) is the correct approach for non-critical cleanup operations that should not fail the job. The job's documented scope—import_balances_only, dedup, merge, and update last_synced_at—is properly implemented without requiring additional orchestration that doesn't exist in the codebase.

@jjmata jjmata requested a review from sokie October 31, 2025 19:41
@jjmata
Copy link
Collaborator

jjmata commented Oct 31, 2025

How do you guys feel about the remaining bot-comments @luckyPipewrench and @sokie?

@sokie
Copy link
Collaborator

sokie commented Oct 31, 2025

How do you guys feel about the remaining bot-comments @luckyPipewrench and @sokie?

I am waiting for @luckyPipewrench to finish all the changes and once ready I'll do a proper reveiw

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/controllers/simplefin_items_controller.rb (1)

126-129: Use the compute_unlinked_count helper for consistency.

Lines 126-129 duplicate the query logic that's already extracted in the compute_unlinked_count helper (from SimplefinItems::RelinkHelpers). The update action uses the helper at line 60, but create doesn't.

Apply this diff:

-        unlinked = @simplefin_item.simplefin_accounts
-          .left_joins(:account, :account_provider)
-          .where(accounts: { id: nil }, account_providers: { id: nil })
-          .count
+        unlinked = compute_unlinked_count(@simplefin_item)
🧹 Nitpick comments (6)
app/controllers/concerns/simplefin_items/relink_helpers.rb (2)

25-25: Consider logging dedup failures instead of silently swallowing them.

The rescue nil suppresses all errors from dedup_simplefin_accounts!, which could hide legitimate issues like database connectivity problems or data integrity violations.

Replace with:

-    @simplefin_item.dedup_simplefin_accounts! rescue nil
+    begin
+      @simplefin_item.dedup_simplefin_accounts!
+    rescue => e
+      Rails.logger.warn("SimpleFin compute_relink_candidates: dedup failed: #{e.class} - #{e.message}")
+    end

66-72: Consider narrowing the rescue scope to specific exceptions.

The bare rescue on line 71 catches all exceptions including system errors. Since this is handling BigDecimal conversion and arithmetic, consider rescuing ArgumentError or TypeError specifically.

Apply this diff:

-              rescue
+              rescue ArgumentError, TypeError

The same applies to the rescue block at lines 86-88.

app/jobs/simplefin_item/balances_only_job.rb (2)

16-22: Clarify why ArgumentError is rescued alongside SimplefinError.

Line 20 rescues ArgumentError alongside provider-specific errors. If this is expected (e.g., from invalid access URL parsing), document it with a comment; otherwise, consider removing it to avoid masking unrelated argument errors.


44-53: Consider extracting the Turbo broadcast logic to a helper method.

The inline ERB template construction (lines 46-49) is fragile and mixes concerns. Since this pattern may be reused, extract it to a helper method in the controller or a service object.

For example, in the controller or a concern:

def broadcast_relink_modal_refresh(item)
  url = relink_simplefin_item_path(item)
  html = ApplicationController.render(
    inline: "<turbo-frame id='modal' src='#{ERB::Util.html_escape(url)}'></turbo-frame>",
    formats: [:html]
  )
  Turbo::StreamsChannel.broadcast_replace_to(item.family, target: "modal", html: html)
end
test/controllers/simplefin_items_controller_test.rb (1)

67-70: Consider extracting the URI parsing pattern to a test helper.

The pattern of parsing response.redirect_url and asserting on uri.path is repeated at lines 67-70, 158-161, and 230-232. Extract this to a helper method to reduce duplication.

For example:

def assert_redirected_to_path(expected_path)
  assert_response :redirect
  uri = URI(response.redirect_url)
  assert_equal expected_path, uri.path
end

Then use: assert_redirected_to_path("/accounts")

app/controllers/simplefin_items_controller.rb (1)

1-1: Remove unused require or document its purpose.

Line 1 adds require "set" but Set is not used anywhere in this controller. Either remove it or add a comment explaining why it's needed for future use.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7848530 and 43270ac.

📒 Files selected for processing (5)
  • app/controllers/concerns/simplefin_items/relink_helpers.rb (1 hunks)
  • app/controllers/simplefin_items_controller.rb (7 hunks)
  • app/jobs/simplefin_item/balances_only_job.rb (1 hunks)
  • app/views/simplefin_items/relink.html.erb (1 hunks)
  • test/controllers/simplefin_items_controller_test.rb (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/views/simplefin_items/relink.html.erb
🧰 Additional context used
📓 Path-based instructions (5)
test/**/*_test.rb

📄 CodeRabbit inference engine (AGENTS.md)

Name Minitest files with *_test.rb and mirror the app/ structure under test/

test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.

test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test

Files:

  • test/controllers/simplefin_items_controller_test.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • test/controllers/simplefin_items_controller_test.rb
  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/concerns/simplefin_items/relink_helpers.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • test/controllers/simplefin_items_controller_test.rb
  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/concerns/simplefin_items/relink_helpers.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/concerns/simplefin_items/relink_helpers.rb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/controllers/simplefin_items_controller.rb
  • app/controllers/concerns/simplefin_items/relink_helpers.rb
🧠 Learnings (23)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/**/*_test.rb : ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.turbo_stream.erb : Use Turbo Streams to enhance functionality but avoid hard dependencies on them

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions

Applied to files:

  • test/controllers/simplefin_items_controller_test.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/{holding/base_calculator.rb,account/balance/base_calculator.rb} : For investment accounts, compute daily holdings via Holding::BaseCalculator and roll them into balances via Account::Balance::BaseCalculator

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/jobs/simplefin_item/balances_only_job.rb
  • app/controllers/simplefin_items_controller.rb
  • app/controllers/concerns/simplefin_items/relink_helpers.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/controllers/simplefin_items_controller.rb
  • app/controllers/concerns/simplefin_items/relink_helpers.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/models/concerns/**/*.rb : Use Rails concerns organized around model traits (not as generic code dumps)

Applied to files:

  • app/controllers/concerns/simplefin_items/relink_helpers.rb
🧬 Code graph analysis (3)
app/jobs/simplefin_item/balances_only_job.rb (3)
app/models/simplefin_item/provided.rb (1)
  • simplefin_provider (4-6)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (2)
  • dedup_simplefin_accounts! (163-226)
  • merge_duplicate_provider_accounts! (230-274)
app/controllers/simplefin_items_controller.rb (5)
app/models/simplefin_item.rb (3)
  • include (1-301)
  • destroy_later (36-39)
  • dedup_simplefin_accounts! (163-226)
app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
  • compute_unlinked_count (10-15)
  • compute_relink_candidates (23-114)
app/models/simplefin_item/relink_service.rb (2)
  • new (3-122)
  • apply! (14-121)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/concerns/simplefin_items/relink_helpers.rb (1)
app/models/simplefin_item.rb (1)
  • dedup_simplefin_accounts! (163-226)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (13)
app/controllers/concerns/simplefin_items/relink_helpers.rb (3)

10-15: LGTM!

The unlinked count query correctly identifies SimpleFin accounts with no Account link (legacy FK) and no AccountProvider link (new polymorphic).


17-21: LGTM!

The name normalization logic is appropriate for fuzzy matching and handles nil/empty inputs safely.


39-109: Well-designed matching heuristics with ambiguity guards.

The three-tier matching strategy (last4 → balance proximity → normalized name) with ambiguity guards (skipping when multiple candidates match) effectively balances automation with safety. The used_manual_ids tracking prevents duplicate assignments.

app/jobs/simplefin_item/balances_only_job.rb (1)

6-41: Defensive error handling pattern prevents cascading failures.

Each operation (import, dedup, merge, timestamp update) is isolated in its own rescue block, ensuring that failures in one step don't prevent subsequent cleanup or UI updates. This is appropriate for a best-effort background job.

test/controllers/simplefin_items_controller_test.rb (4)

244-256: Good coverage of destroy unlinking behavior.

The test verifies that destroying a SimplefinItem immediately unlinks provider links and legacy FK references, even though the actual deletion is scheduled. This confirms the fix for the unlink-before-delete requirement.


258-280: Good coverage of apply_relink deduplication.

The test validates that apply_relink moves the provider link to the manual account and removes the duplicate provider-backed account, confirming the relink service cleanup behavior.


282-307: Good coverage of selective account creation during setup.

The test confirms that complete_account_setup only creates accounts for truly unlinked SimpleFin accounts while preserving existing links, preventing duplicate account creation.


324-346: Good coverage of Turbo Stream response format.

The test validates that apply_relink responds with proper Turbo Stream content including remove and replace actions, confirming the multi-format response support.

app/controllers/simplefin_items_controller.rb (5)

55-88: Well-structured post-update flow with auto-relink support.

The update action now schedules a balances-only job for async data refresh, computes relink candidates, and conditionally opens the relink modal when matches are found. The multi-format response support (HTML/Turbo/JSON) enables flexible frontend integration.


121-153: Well-structured post-create flow with relink discovery.

The create action schedules a balances-only job and computes relink candidates, presenting the relink modal immediately when matches exist. This improves the onboarding UX by surfacing relinking opportunities early.


171-179: Good defensive unlinking before deletion.

The destroy action now uses SimplefinItem::Unlinker to remove provider links before scheduling deletion, ensuring accounts move to "Other accounts" section immediately. The rescue block prevents deletion failures from blocking the user flow.


193-202: Balances-only sync action provides lightweight refresh capability.

The new balances action creates a sync with balances_only stats and performs it synchronously. While this runs in-request, the earlier past review comments indicate this was addressed by making it async elsewhere (BalancesOnlyJob), though here it's still synchronous.

Note: Earlier feedback flagged synchronous SimpleFin API calls as a reliability risk. Verify whether this action is intentionally kept synchronous (e.g., for immediate user feedback) or should dispatch BalancesOnlyJob instead.


291-357: New actions provide comprehensive error reporting and relinking UX.

The errors, relink, manual_relink, and apply_relink actions support modal-based workflows for viewing per-account errors, auto-suggesting relinks, manually selecting relinks, and applying them with multi-format responses (HTML/Turbo/JSON).

@luckyPipewrench
Copy link
Collaborator Author

How do you guys feel about the remaining bot-comments @luckyPipewrench and @sokie?

I am waiting for @luckyPipewrench to finish all the changes and once ready I'll do a proper reveiw

@sokie Sorry, my discord has limited access for a couple days,,,, But thanks for the context. Sorry I missed your DM before pinging you for review! #268 is awesome. De-dup should be strict enough now, linking is manual-only, user selects via check boxes in modal, not auto. Will tighten if needed. On generalization, the modal is simplefin-specific for now, planning to make it provider-agnostic in a follow up later unless you think I should change it before it merges.

@sokie
Copy link
Collaborator

sokie commented Nov 2, 2025

How do you guys feel about the remaining bot-comments @luckyPipewrench and @sokie?

I am waiting for @luckyPipewrench to finish all the changes and once ready I'll do a proper reveiw

@sokie Sorry, my discord has limited access for a couple days,,,, But thanks for the context. Sorry I missed your DM before pinging you for review! #268 is awesome. De-dup should be strict enough now, linking is manual-only, user selects via check boxes in modal, not auto. Will tighten if needed. On generalization, the modal is simplefin-specific for now, planning to make it provider-agnostic in a follow up later unless you think I should change it before it merges.

Sounds good!
First pass i did with the previous feedback, tbh i think this is in a really good place already now.
I'll do a thorough pass tomorrow and leave a review.
PS: resolve all the fixed items from Coderabbit so we know what is left or not.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (5)
app/views/simplefin_items/_simplefin_item.html.erb (4)

46-46: Address the bare rescue from past review.

A past review comment flagged this bare rescue which catches all exceptions including SystemExit and SignalException. Replace with rescue StandardError.


211-211: Address bare rescue from past reviews.

These lines still use bare rescue which was flagged in past review comments. Replace with rescue StandardError to avoid catching system exceptions.

Also applies to: 272-272


227-227: Move sync query to controller.

After removing the duplicate section, this simplefin_item.syncs.ordered.first query should be moved to the controller to avoid N+1 queries and keep domain logic out of views, as previously suggested in past review comments.

As per coding guidelines.


288-291: Move unlinked account query to controller.

This database query computing unlinked_count was flagged in past review comments and should be moved to the controller to keep domain logic out of views.

As per coding guidelines.

db/schema.rb (1)

28-30: Drop the duplicate unique index on account_providers.

We still emit two unique indexes on (account_id, provider_type) (index_account_providers_on_account_and_provider_type and index_account_providers_on_account_id_and_provider_type). That doubles constraint checks/storage without adding coverage. Please update the migration to drop/rename the legacy index instead of creating a second copy and regenerate the schema.

🧹 Nitpick comments (6)
app/models/provider/openai.rb (1)

57-61: Good fail-fast guard with clear logging.

The guard prevents wasteful API calls when categories are unavailable and includes defensive family_id logging. The placement before effective_model computation is optimal.

Consider enhancing the user-facing error message with actionable guidance:

-        raise Error, "No categories available for auto-categorization"
+        raise Error, "No categories available for auto-categorization. Please create categories before using this feature."
app/models/family/auto_categorizer.rb (1)

19-24: Optional: Validation duplicates provider-level check.

The empty categories guard duplicates validation already in Provider::OpenAI#auto_categorize, which raises an error when user_categories.blank?. While defensive checks can be beneficial, this duplication means the validation logic lives in two places.

Consider removing this guard to keep validation centralized, or document why the early return is preferred over the provider's exception.

app/models/family/sync_complete_event.rb (1)

32-37: Consider moving pattern identification to a background job.

Running RecurringTransaction.identify_patterns_for(family) synchronously after every sync could impact performance, especially for families with large transaction histories. This blocks the broadcast method and delays sync completion.

Consider enqueuing pattern identification as a background job to avoid blocking the sync completion flow:

-    # Identify recurring transaction patterns after sync
-    begin
-      RecurringTransaction.identify_patterns_for(family)
-    rescue => e
-      Rails.logger.error("Family::SyncCompleteEvent recurring transaction identification failed: #{e.message}\n#{e.backtrace&.join("\n")}")
-    end
+    # Identify recurring transaction patterns after sync in background
+    IdentifyRecurringPatternsJob.perform_later(family.id)

This would allow the sync to complete immediately while pattern identification runs asynchronously.

config/locales/views/recurring_transactions/en.yml (1)

1-38: LGTM with minor formatting suggestion.

The translation file is comprehensive and well-structured. All interpolation variables follow Rails conventions, and the content is clear and user-friendly.

Consider fixing minor spacing inconsistencies in the trigger descriptions:

       triggers:
-        - CSV imports complete (transactions, trades, accounts, etc. )
-        - Any provider sync completes ( Plaid, SimpleFIN, etc. )
+        - CSV imports complete (transactions, trades, accounts, etc.)
+        - Any provider sync completes (Plaid, SimpleFIN, etc.)
app/controllers/transactions_controller.rb (1)

26-32: Consider reordering the WHERE clause for readability.

The logic is correct but the condition order reads unnaturally. The query searches for dates between today and one month from now, but the SQL conditions are reversed.

Apply this diff for improved clarity:

-    @projected_recurring = Current.family.recurring_transactions
-                                  .active
-                                  .where("next_expected_date <= ? AND next_expected_date >= ?",
-                                         1.month.from_now.to_date,
-                                         Date.current)
-                                  .includes(:merchant)
+    @projected_recurring = Current.family.recurring_transactions
+                                  .active
+                                  .where("next_expected_date >= ? AND next_expected_date <= ?",
+                                         Date.current,
+                                         1.month.from_now.to_date)
+                                  .includes(:merchant)

Or use BETWEEN for even better readability:

-    @projected_recurring = Current.family.recurring_transactions
-                                  .active
-                                  .where("next_expected_date <= ? AND next_expected_date >= ?",
-                                         1.month.from_now.to_date,
-                                         Date.current)
-                                  .includes(:merchant)
+    @projected_recurring = Current.family.recurring_transactions
+                                  .active
+                                  .where("next_expected_date BETWEEN ? AND ?",
+                                         Date.current,
+                                         1.month.from_now.to_date)
+                                  .includes(:merchant)
test/models/recurring_transaction_test.rb (1)

63-77: Consider adding a leap year test case.

The end-of-month handling test is solid. For comprehensive coverage, consider adding a test for leap years (e.g., Jan 31 → Feb 29 in 2024) to ensure the date calculation handles that edge case correctly.

Example test:

test "calculate_next_expected_date handles leap year correctly" do
  recurring = @family.recurring_transactions.create!(
    merchant: @merchant,
    amount: 29.99,
    currency: "USD",
    expected_day_of_month: 31,
    last_occurrence_date: Date.new(2024, 1, 31),
    next_expected_date: Date.new(2024, 2, 29),
    status: "active"
  )

  next_date = recurring.calculate_next_expected_date(Date.new(2024, 1, 31))
  assert_equal Date.new(2024, 2, 29), next_date
end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43270ac and 6c49b55.

📒 Files selected for processing (42)
  • app/controllers/recurring_transactions_controller.rb (1 hunks)
  • app/controllers/transactions_controller.rb (1 hunks)
  • app/models/balance_sheet/classification_group.rb (1 hunks)
  • app/models/family.rb (1 hunks)
  • app/models/family/auto_categorizer.rb (2 hunks)
  • app/models/family/sync_complete_event.rb (1 hunks)
  • app/models/merchant.rb (1 hunks)
  • app/models/provider/openai.rb (1 hunks)
  • app/models/recurring_transaction.rb (1 hunks)
  • app/models/recurring_transaction/cleaner.rb (1 hunks)
  • app/models/recurring_transaction/identifier.rb (1 hunks)
  • app/models/simplefin_account/processor.rb (1 hunks)
  • app/models/simplefin_entry/processor.rb (3 hunks)
  • app/views/accounts/_account_sidebar_tabs.html.erb (3 hunks)
  • app/views/accounts/_accountable_group.html.erb (1 hunks)
  • app/views/family_exports/_list.html.erb (2 hunks)
  • app/views/imports/_import.html.erb (1 hunks)
  • app/views/imports/index.html.erb (2 hunks)
  • app/views/layouts/_dark_mode_check.html.erb (1 hunks)
  • app/views/pages/dashboard/_outflows_donut.html.erb (3 hunks)
  • app/views/recurring_transactions/_projected_transaction.html.erb (1 hunks)
  • app/views/recurring_transactions/index.html.erb (1 hunks)
  • app/views/settings/_settings_nav.html.erb (1 hunks)
  • app/views/settings/ai_prompts/show.html.erb (4 hunks)
  • app/views/settings/bank_sync/_provider_link.html.erb (1 hunks)
  • app/views/settings/bank_sync/show.html.erb (0 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (3 hunks)
  • app/views/simplefin_items/relink.html.erb (1 hunks)
  • app/views/trades/_form.html.erb (1 hunks)
  • app/views/transactions/index.html.erb (2 hunks)
  • config/locales/views/accounts/ca.yml (1 hunks)
  • config/locales/views/accounts/en.yml (1 hunks)
  • config/locales/views/recurring_transactions/en.yml (1 hunks)
  • config/locales/views/settings/en.yml (1 hunks)
  • config/routes.rb (3 hunks)
  • db/migrate/20251031132654_create_recurring_transactions.rb (1 hunks)
  • db/schema.rb (8 hunks)
  • test/fixtures/recurring_transactions.yml (1 hunks)
  • test/models/balance_sheet_test.rb (2 hunks)
  • test/models/recurring_transaction/identifier_test.rb (1 hunks)
  • test/models/recurring_transaction_test.rb (1 hunks)
  • test/system/settings/ai_prompts_test.rb (1 hunks)
💤 Files with no reviewable changes (1)
  • app/views/settings/bank_sync/show.html.erb
✅ Files skipped from review due to trivial changes (5)
  • app/views/imports/_import.html.erb
  • app/views/settings/bank_sync/_provider_link.html.erb
  • app/views/family_exports/_list.html.erb
  • app/views/pages/dashboard/_outflows_donut.html.erb
  • app/views/layouts/_dark_mode_check.html.erb
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/simplefin_items/relink.html.erb
  • config/routes.rb
🧰 Additional context used
📓 Path-based instructions (25)
app/models/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods

Use ActiveRecord validations for forms and complex domain constraints.

app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required

Files:

  • app/models/family/sync_complete_event.rb
  • app/models/family.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/models/merchant.rb
  • app/models/recurring_transaction/identifier.rb
  • app/models/simplefin_account/processor.rb
  • app/models/provider/openai.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/models/family/auto_categorizer.rb
  • app/models/balance_sheet/classification_group.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/models/family/sync_complete_event.rb
  • app/models/family.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/models/merchant.rb
  • app/controllers/transactions_controller.rb
  • app/models/recurring_transaction/identifier.rb
  • app/models/simplefin_account/processor.rb
  • app/models/provider/openai.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/models/family/auto_categorizer.rb
  • app/controllers/recurring_transactions_controller.rb
  • app/models/balance_sheet/classification_group.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • app/models/family/sync_complete_event.rb
  • test/system/settings/ai_prompts_test.rb
  • app/models/family.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/models/merchant.rb
  • app/controllers/transactions_controller.rb
  • app/models/recurring_transaction/identifier.rb
  • app/models/simplefin_account/processor.rb
  • db/migrate/20251031132654_create_recurring_transactions.rb
  • app/models/provider/openai.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/models/family/auto_categorizer.rb
  • app/controllers/recurring_transactions_controller.rb
  • test/models/recurring_transaction_test.rb
  • app/models/balance_sheet/classification_group.rb
  • test/models/recurring_transaction/identifier_test.rb
  • test/models/balance_sheet_test.rb
  • db/schema.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • app/models/family/sync_complete_event.rb
  • test/system/settings/ai_prompts_test.rb
  • app/models/family.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/models/merchant.rb
  • app/controllers/transactions_controller.rb
  • app/models/recurring_transaction/identifier.rb
  • app/models/simplefin_account/processor.rb
  • db/migrate/20251031132654_create_recurring_transactions.rb
  • app/models/provider/openai.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/models/family/auto_categorizer.rb
  • app/controllers/recurring_transactions_controller.rb
  • test/models/recurring_transaction_test.rb
  • app/models/balance_sheet/classification_group.rb
  • test/models/recurring_transaction/identifier_test.rb
  • test/models/balance_sheet_test.rb
  • db/schema.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/models/family/sync_complete_event.rb
  • app/models/family.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/models/merchant.rb
  • app/models/recurring_transaction/identifier.rb
  • app/models/simplefin_account/processor.rb
  • app/models/provider/openai.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/models/family/auto_categorizer.rb
  • app/models/balance_sheet/classification_group.rb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/models/family/sync_complete_event.rb
  • app/views/accounts/_accountable_group.html.erb
  • app/models/family.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/models/merchant.rb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/controllers/transactions_controller.rb
  • app/models/recurring_transaction/identifier.rb
  • app/models/simplefin_account/processor.rb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/models/provider/openai.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/models/family/auto_categorizer.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/controllers/recurring_transactions_controller.rb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/models/balance_sheet/classification_group.rb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/transactions/index.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/recurring_transactions/index.html.erb
  • app/views/imports/index.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/views/settings/ai_prompts/show.html.erb
app/views/**/_*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Files:

  • app/views/accounts/_accountable_group.html.erb
  • app/views/trades/_form.html.erb
  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
  • app/views/settings/_settings_nav.html.erb
test/**/*_test.rb

📄 CodeRabbit inference engine (AGENTS.md)

Name Minitest files with *_test.rb and mirror the app/ structure under test/

test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.

test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test

Files:

  • test/system/settings/ai_prompts_test.rb
  • test/models/recurring_transaction_test.rb
  • test/models/recurring_transaction/identifier_test.rb
  • test/models/balance_sheet_test.rb
test/system/**/*_test.rb

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

Use system tests sparingly to keep the suite fast

Files:

  • test/system/settings/ai_prompts_test.rb
config/**

📄 CodeRabbit inference engine (AGENTS.md)

Store application and environment configuration under config/

Files:

  • config/locales/views/settings/en.yml
  • config/locales/views/accounts/ca.yml
  • config/locales/views/accounts/en.yml
  • config/locales/views/recurring_transactions/en.yml
db/migrate/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place database migrations under db/migrate/

Simple validations like null checks and unique constraints should be handled by database constraints in migration files.

Files:

  • db/migrate/20251031132654_create_recurring_transactions.rb
db/migrate/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Enforce simple validations in the database (NULL constraints, unique indexes, etc.)

Files:

  • db/migrate/20251031132654_create_recurring_transactions.rb
test/fixtures/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Use test/fixtures for fixtures (and existing VCR cassettes if present)

Files:

  • test/fixtures/recurring_transactions.yml
test/fixtures/**/*.yml

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Keep fixtures minimal: 2–3 per model for base cases.

test/fixtures/**/*.yml: Use fixtures for test data rather than factories
Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests

Files:

  • test/fixtures/recurring_transactions.yml
app/models/provider/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Concrete provider classes must be under the Provider:: namespace, inherit from Provider, wrap calls with with_provider_response, and raise on invalid/unavailable data

Files:

  • app/models/provider/openai.rb
db/schema.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Treat db/schema.rb as the authoritative source of truth for model relationships

Files:

  • db/schema.rb
🧠 Learnings (36)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/models/family/sync_complete_event.rb
  • app/models/recurring_transaction/cleaner.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • app/models/family/sync_complete_event.rb
  • app/controllers/transactions_controller.rb
  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_entry/processor.rb
  • app/models/recurring_transaction.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/models/**/*.rb : Implement complex validations and business logic with ActiveRecord validations

Applied to files:

  • app/models/family.rb
  • app/models/merchant.rb
📚 Learning: 2025-08-23T05:26:38.919Z
Learnt from: jjmata
Repo: we-promise/sure PR: 89
File: app/views/trades/_form.html.erb:53-53
Timestamp: 2025-08-23T05:26:38.919Z
Learning: In the we-promise/sure Rails application, there is a deliberate distinction between storage precision and display precision for Trade.price: the database stores up to 10 decimal places to avoid rounding errors in calculations, but UI display should use default currency precision (typically 2-4 decimals) for readability. Input forms can allow high precision entry, but display views should not show excessive decimal places.

Applied to files:

  • app/views/trades/_form.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{helpers,models}/**/*.rb : Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Applied to files:

  • app/views/trades/_form.html.erb
  • config/locales/views/recurring_transactions/en.yml
  • app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • test/models/balance_sheet_test.rb
  • db/schema.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_entry/processor.rb
  • test/models/balance_sheet_test.rb
  • db/schema.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/models/simplefin_account/processor.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to app/views/**/_*.html.erb : Name partials with a leading underscore (e.g., _trend_change.html.erb, _form_errors.html.erb)

Applied to files:

  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use partials when content is primarily static, simple, context-specific, and without variants or complex configuration

Applied to files:

  • app/views/recurring_transactions/_projected_transaction.html.erb
  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to db/migrate/**/*.rb : Enforce simple validations in the database (NULL constraints, unique indexes, etc.)

Applied to files:

  • db/migrate/20251031132654_create_recurring_transactions.rb
  • db/schema.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories

Applied to files:

  • test/fixtures/recurring_transactions.yml
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests

Applied to files:

  • test/fixtures/recurring_transactions.yml
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.

Applied to files:

  • test/fixtures/recurring_transactions.yml
📚 Learning: 2025-10-07T09:51:02.265Z
Learnt from: jjmata
Repo: we-promise/sure PR: 189
File: app/views/password_resets/new.html.erb:14-14
Timestamp: 2025-10-07T09:51:02.265Z
Learning: In the we-promise/sure repository, use i18n methods (like `t()` or `I18n.t`) for all user-facing strings to maintain proper internationalization infrastructure. While translations should be provided, extensive localization work is not a priority at this stage of development.

Applied to files:

  • config/locales/views/recurring_transactions/en.yml
  • app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-10-01T11:40:36.711Z
Learnt from: jjmata
Repo: we-promise/sure PR: 173
File: app/mailers/email_confirmation_mailer.rb:9-9
Timestamp: 2025-10-01T11:40:36.711Z
Learning: In the we-promise/sure repository, i18n (internationalization) is used throughout the application for translations. When making branding configurable (e.g., product names), it's appropriate to add parameters like `product: product_name` to existing `t()` translation calls rather than replacing them with hardcoded strings.

Applied to files:

  • config/locales/views/recurring_transactions/en.yml
  • app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to app/views/**/*.erb : Keep heavy logic out of ERB views; prefer helpers/components instead

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Keep domain logic out of ERB templates; compute classes and logic in component/Ruby code, not inline in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/**/*.erb : Be mindful of performance in global layouts (e.g., avoid loading large data payloads)

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • db/schema.rb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Place context-specific partials within the relevant controller view directory (e.g., accounts/_account_sidebar_tabs.html.erb)

Applied to files:

  • app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Prefer native HTML elements (e.g., <dialog>, <details><summary>) over JS-based components

Applied to files:

  • app/views/accounts/_account_sidebar_tabs.html.erb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior

Applied to files:

  • test/models/recurring_transaction_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test

Applied to files:

  • test/models/recurring_transaction_test.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • test/models/balance_sheet_test.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated

Applied to files:

  • app/views/imports/index.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions

Applied to files:

  • app/views/imports/index.html.erb
📚 Learning: 2025-08-22T18:30:26.758Z
Learnt from: jjmata
Repo: we-promise/sure PR: 116
File: app/views/family_exports/_list.html.erb:16-28
Timestamp: 2025-08-22T18:30:26.758Z
Learning: In the family exports UI (app/views/family_exports/_list.html.erb), raw color utilities like bg-green-500/5, text-green-500, bg-red-500/5, text-red-500 are preferred over design system tokens for status indicators to maintain UI legibility and consistency with the existing import UI patterns. The user has indicated that design token replacements would make the UI illegible in this context.

Applied to files:

  • app/views/imports/index.html.erb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to db/schema.rb : Treat db/schema.rb as the authoritative source of truth for model relationships

Applied to files:

  • db/schema.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to db/migrate/*.rb : Simple validations like null checks and unique constraints should be handled by database constraints in migration files.

Applied to files:

  • db/schema.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to db/seeds.rb : Keep seed data definitions in db/seeds.rb

Applied to files:

  • db/schema.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • db/schema.rb
📚 Learning: 2025-08-22T23:27:46.033Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: db/migrate/20250813144520_add_institution_fields_to_simplefin_items.rb:6-6
Timestamp: 2025-08-22T23:27:46.033Z
Learning: Institution URLs in financial contexts are typically short (under 100 characters) and don't require text columns - varchar(255) via :string is sufficient for SimpleFin institution URLs.

Applied to files:

  • db/schema.rb
🧬 Code graph analysis (15)
app/models/family/sync_complete_event.rb (2)
app/models/recurring_transaction.rb (1)
  • identify_patterns_for (19-21)
app/models/sync.rb (1)
  • family (192-198)
app/models/family.rb (1)
app/models/category.rb (1)
  • has_many (1-149)
app/models/recurring_transaction/cleaner.rb (1)
app/models/recurring_transaction.rb (2)
  • matching_transactions (28-42)
  • mark_inactive! (51-53)
app/controllers/transactions_controller.rb (1)
app/controllers/accounts_controller.rb (1)
  • family (91-93)
app/models/recurring_transaction/identifier.rb (3)
app/models/family/sync_complete_event.rb (2)
  • attr_reader (1-39)
  • initialize (4-6)
app/models/recurring_transaction/cleaner.rb (3)
  • attr_reader (1-41)
  • attr_reader (2-40)
  • initialize (5-7)
app/models/recurring_transaction.rb (1)
  • calculate_next_expected_date (70-81)
app/models/simplefin_account/processor.rb (2)
app/models/simplefin_account.rb (2)
  • current_account (16-18)
  • ensure_account_provider! (22-34)
app/models/plaid_account/processor.rb (2)
  • process_account! (31-95)
  • family (22-24)
app/models/provider/openai.rb (1)
app/models/assistant/function.rb (1)
  • family (78-80)
app/models/simplefin_entry/processor.rb (2)
app/models/account/provider_import_adapter.rb (1)
  • attr_reader (1-314)
app/models/simplefin_account/transactions/processor.rb (2)
  • attr_reader (1-42)
  • account (39-41)
app/models/recurring_transaction.rb (5)
app/controllers/transactions_controller.rb (2)
  • include (1-196)
  • new (6-10)
app/models/family.rb (1)
  • include (1-127)
app/models/concerns/monetizable.rb (1)
  • monetize (5-15)
app/models/recurring_transaction/identifier.rb (2)
  • identify_recurring_patterns (10-73)
  • calculate_next_expected_date (148-157)
app/models/recurring_transaction/cleaner.rb (1)
  • cleanup_stale_transactions (10-29)
app/models/family/auto_categorizer.rb (2)
app/models/family/auto_merchant_detector.rb (2)
  • llm_provider (65-67)
  • transactions_input (82-92)
app/models/provider/openai.rb (1)
  • auto_categorize (54-84)
app/controllers/recurring_transactions_controller.rb (2)
app/controllers/transactions_controller.rb (1)
  • index (12-33)
app/models/recurring_transaction.rb (4)
  • identify_patterns_for (19-21)
  • cleanup_stale_for (23-25)
  • mark_inactive! (51-53)
  • mark_active! (56-58)
test/models/recurring_transaction_test.rb (3)
test/models/recurring_transaction/identifier_test.rb (2)
  • setup (3-248)
  • setup (4-8)
app/models/recurring_transaction.rb (5)
  • identify_patterns_for (19-21)
  • calculate_next_expected_date (70-81)
  • should_be_inactive? (45-48)
  • cleanup_stale_for (23-25)
  • record_occurrence! (61-67)
app/models/recurring_transaction/identifier.rb (1)
  • calculate_next_expected_date (148-157)
test/models/recurring_transaction/identifier_test.rb (2)
test/models/recurring_transaction_test.rb (2)
  • setup (3-172)
  • setup (4-9)
app/models/recurring_transaction/identifier.rb (1)
  • identify_recurring_patterns (10-73)
test/models/balance_sheet_test.rb (2)
app/models/balance_sheet/classification_group.rb (2)
  • name (15-17)
  • total (23-25)
app/models/balance_sheet/account_group.rb (1)
  • total (36-38)
db/schema.rb (2)
app/controllers/recurring_transactions_controller.rb (1)
  • index (4-8)
app/controllers/transactions_controller.rb (1)
  • index (12-33)
🪛 ast-grep (0.39.6)
app/controllers/transactions_controller.rb

[warning] 26-30: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: Current.family.recurring_transactions
.active
.where("next_expected_date <= ? AND next_expected_date >= ?",
1.month.from_now.to_date,
Date.current)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

app/models/recurring_transaction.rb

[warning] 15-15: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: active.where("next_expected_date <= ?", 1.month.from_now)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 28-34: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: family.entries
.where(entryable_type: "Transaction")
.where(currency: currency)
.where("entries.amount = ?", amount)
.where("EXTRACT(DAY FROM entries.date) BETWEEN ? AND ?",
[ expected_day_of_month - 2, 1 ].max,
[ expected_day_of_month + 2, 31 ].min)
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/views/simplefin_items/relink.html.erb (2)

12-12: Use functional design tokens instead of raw color classes.

The divide-gray-200 and border-gray-200 classes are raw Tailwind colors. Per the coding guidelines, prefer functional tokens from the design system such as border-primary or divide-primary.

As per coding guidelines.

Apply this diff:

-        <div class="divide-y divide-gray-200 rounded-md border border-gray-200">
+        <div class="divide-y divide-primary rounded-md border border-primary">

37-37: Use functional design tokens instead of raw color classes.

Same as line 12: prefer functional tokens like border-primary or divide-primary over raw border-gray-200 and divide-gray-200.

As per coding guidelines.

Apply this diff:

-          <div class="mt-2 divide-y divide-gray-200 rounded-md border border-gray-200">
+          <div class="mt-2 divide-y divide-primary rounded-md border border-primary">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c49b55 and 40d383e.

📒 Files selected for processing (1)
  • app/views/simplefin_items/relink.html.erb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/simplefin_items/relink.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/simplefin_items/relink.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/views/simplefin_items/relink.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/simplefin_items/relink.html.erb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/views/simplefin_items/relink.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/views/simplefin_items/relink.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/simplefin_items/relink.html.erb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/simplefin_items/relink.html.erb
🧠 Learnings (5)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:24:15.106Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:24:15.106Z
Learning: Applies to {app/views,app/components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated

Applied to files:

  • app/views/simplefin_items/relink.html.erb
🔇 Additional comments (2)
app/views/simplefin_items/relink.html.erb (2)

1-7: LGTM! Modal structure follows Hotwire conventions.

The Turbo Frame wrapping and declarative Stimulus action for closing the modal align with the project's Hotwire integration patterns.


13-13: The original review comment incorrectly flags N+1 query concerns that are not present in the code:

  1. @candidates is pre-computed as an array of plain hashes (with keys :sfa_id, :sfa_name, :manual_id, :manual_name) by compute_relink_candidates. The view accesses only hash keys—no AR objects or associations involved.

  2. @unlinked_sfas uses left_joins(:account, :account_provider) with filtering and the view accesses only direct attributes (.id, .name), which are not associations.

  3. @manual_accounts uses left_joins(:account_providers) with filtering and the view accesses only direct attributes (.id, .name), which are not associations.

The controller properly applies preloading via explicit joins before filtering. No N+1 queries will occur.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
lib/tasks/simplefin_backfill.rake (3)

81-81: Critical blocker still present: find_each will crash when sfas is an Array.

When account_id is provided (line 62-69), sfas becomes Array.wrap(sfa) at line 69, so calling sfas.find_each at line 81 raises NoMethodError because Arrays don't have a find_each method.

Apply the fix from the previous review:

-      sfas.find_each do |sfa|
+      Array(sfas).each do |sfa|

Or retain batch behavior for Relations:

+      sfas = sfas.is_a?(ActiveRecord::Relation) ? sfas : SimplefinAccount.where(id: Array.wrap(sfas).compact.map(&:id))
       sfas.find_each do |sfa|

75-79: Major issue still present: Per-SFA summary uses cumulative totals.

The counters total_seen, total_matched, total_updated, total_skipped, and total_errors are initialized once (lines 75-79) and incremented throughout the entire loop. At line 174, the per-SFA output prints these cumulative values, misleading operators into thinking they're per-SFA counts.

Introduce per-SFA counters as suggested in the previous review to provide accurate per-account reporting while maintaining global totals.

Also applies to: 174-174


150-167: Minor issues still present: Guard external_id and avoid duplicate Processor instantiation.

  1. Line 152: If t[:id] is nil, external_id becomes "simplefin_" which is invalid.
  2. Line 157: SimplefinEntry::Processor is instantiated twice in dry_run mode.
  3. Line 157: The composite_match call is missing the window_days parameter.

Apply the fix from the previous review to guard nil IDs, instantiate Processor once, and include window_days.

🧹 Nitpick comments (5)
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)

2-10: Consider adding a safety check to verify the newer index exists.

The migration correctly removes the duplicate legacy index with proper guards. However, for extra safety, consider verifying that the newer index (index_account_providers_on_account_and_provider_type) exists before removing the legacy one. This ensures the table retains the uniqueness constraint even if migrations run out of order.

Apply this diff to add the safety check:

 def up
+  # Verify the newer index exists before removing the legacy one
+  unless index_exists?(:account_providers, [ :account_id, :provider_type ], name: "index_account_providers_on_account_and_provider_type")
+    raise "Cannot remove legacy index: newer index 'index_account_providers_on_account_and_provider_type' does not exist. Run FixAccountProvidersIndexes migration first."
+  end
+
   # We currently have two unique indexes on the same column set (account_id, provider_type):
   #  - index_account_providers_on_account_and_provider_type (added in FixAccountProvidersIndexes)
   #  - index_account_providers_on_account_id_and_provider_type (legacy auto-generated name)
   # Drop the legacy duplicate to avoid redundant constraint checks and storage.
   if index_exists?(:account_providers, [ :account_id, :provider_type ], name: "index_account_providers_on_account_id_and_provider_type")
     remove_index :account_providers, name: "index_account_providers_on_account_id_and_provider_type"
   end
 end
app/models/recurring_transaction.rb (1)

28-42: Optimize to avoid N+1 queries.

The method loads entries from the database, then filters them in Ruby by accessing entry.entryable.merchant_id, which triggers a separate query for each entry. This violates the "Avoid N+1 queries" guideline.

Consider one of these approaches:

Option 1: Eager load the entryable association

 def matching_transactions
   entries = family.entries
+    .includes(:entryable)
     .where(entryable_type: "Transaction")

Option 2 (preferred): Filter merchant at the database level

 def matching_transactions
-  entries = family.entries
+  entries = family.entries
+    .joins("INNER JOIN transactions ON transactions.id = entries.entryable_id")
     .where(entryable_type: "Transaction")
+    .where(transactions: { merchant_id: merchant_id })
     .where(currency: currency)
     .where("entries.amount = ?", amount)
     .where("EXTRACT(DAY FROM entries.date) BETWEEN ? AND ?",
            [ expected_day_of_month - 2, 1 ].max,
            [ expected_day_of_month + 2, 31 ].min)
     .order(date: :desc)
-
-  # Filter by merchant through the entryable (Transaction)
-  entries.select do |entry|
-    entry.entryable.is_a?(Transaction) && entry.entryable.merchant_id == merchant_id
-  end
 end

As per coding guidelines.

lib/tasks/simplefin_backfill.rake (2)

154-171: Consider per-entry transaction wrapping in live mode.

In non-dry-run mode (line 161), if SimplefinEntry::Processor#process partially updates data before failing, the error rescue (lines 168-171) logs the issue but doesn't roll back partial changes. For cleaner error recovery, wrap each entry's processing in an ActiveRecord::Base.transaction block.

Apply this diff to add transaction safety:

             if dry_run
               # ... existing dry_run logic ...
             else
-              processed = SimplefinEntry::Processor.new(t, simplefin_account: sfa).process
-              if processed&.transaction&.extra.present?
-                total_updated += 1
-              else
-                total_skipped += 1
+              ActiveRecord::Base.transaction do
+                processed = SimplefinEntry::Processor.new(t, simplefin_account: sfa).process
+                if processed&.transaction&.extra.present?
+                  total_updated += 1
+                else
+                  total_skipped += 1
+                end
               end
             end

100-125: Consider extracting date parsing logic to a helper method.

The date parsing logic for posted (lines 100-112) and transacted_at (lines 113-125) is identical. Extracting it to a private method would reduce duplication and improve maintainability.

Example refactor:

def parse_date_field(value)
  case value
  when String
    Date.parse(value) rescue nil
  when Numeric
    Time.zone.at(value).to_date rescue nil
  when Date
    value
  when Time, DateTime
    value.to_date
  else
    nil
  end
end

Then use:

posted_d = parse_date_field(t[:posted])
trans_d = parse_date_field(t[:transacted_at])
app/controllers/simplefin_items_controller.rb (1)

1-1: Remove unused require.

The Set class is not referenced anywhere in this controller. Removing the unused require keeps dependencies clean.

Apply this diff:

-require "set"
-
 class SimplefinItemsController < ApplicationController
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40d383e and 706dea5.

📒 Files selected for processing (14)
  • app/controllers/accounts_controller.rb (1 hunks)
  • app/controllers/simplefin_items_controller.rb (8 hunks)
  • app/helpers/simplefin_items_helper.rb (1 hunks)
  • app/models/recurring_transaction.rb (1 hunks)
  • app/models/simplefin_account.rb (1 hunks)
  • app/models/simplefin_account/processor.rb (1 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (3 hunks)
  • app/views/simplefin_items/relink.html.erb (1 hunks)
  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1 hunks)
  • db/schema.rb (5 hunks)
  • lib/tasks/simplefin_backfill.rake (1 hunks)
  • lib/tasks/simplefin_fix_was_merged.rake (1 hunks)
  • lib/tasks/was_merged.rake (1 hunks)
  • test/views/transactions/merged_badge_view_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/controllers/accounts_controller.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • db/schema.rb
🧰 Additional context used
📓 Path-based instructions (18)
db/migrate/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place database migrations under db/migrate/

Simple validations like null checks and unique constraints should be handled by database constraints in migration files.

Files:

  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Files:

  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
  • app/models/simplefin_account.rb
  • test/views/transactions/merged_badge_view_test.rb
  • app/models/recurring_transaction.rb
  • app/controllers/simplefin_items_controller.rb
  • app/helpers/simplefin_items_helper.rb
  • app/models/simplefin_account/processor.rb
**/*.{rb,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{rb,js,jsx,ts,tsx}: Make changes atomic, testable, and explain their impact briefly in code suggestions.
Respect existing tests and add tests when changing critical logic.

Files:

  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
  • app/models/simplefin_account.rb
  • test/views/transactions/merged_badge_view_test.rb
  • app/models/recurring_transaction.rb
  • app/controllers/simplefin_items_controller.rb
  • app/helpers/simplefin_items_helper.rb
  • app/models/simplefin_account/processor.rb
db/migrate/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Enforce simple validations in the database (NULL constraints, unique indexes, etc.)

Files:

  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
app/models/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-design.mdc)

Domain models should not call Provider::Registry directly; use a Provided concern within the model’s namespace to select providers and expose convenience methods

Use ActiveRecord validations for forms and complex domain constraints.

app/models/**/*.rb: Place business logic in POROs and model classes under app/models
Models should answer questions about themselves (e.g., prefer account.balance_series over service objects)
Implement complex validations and business logic with ActiveRecord validations
Model-level validations may mirror DB constraints but are not strictly required

Files:

  • app/models/simplefin_account.rb
  • app/models/recurring_transaction.rb
  • app/models/simplefin_account/processor.rb
app/**/*.rb

📄 CodeRabbit inference engine (AGENTS.md)

Place Rails application Ruby code (models, controllers, services, jobs, mailers, components) under app/

Files:

  • app/models/simplefin_account.rb
  • app/models/recurring_transaction.rb
  • app/controllers/simplefin_items_controller.rb
  • app/helpers/simplefin_items_helper.rb
  • app/models/simplefin_account/processor.rb
app/{helpers,models}/**/*.rb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Format currencies, numbers, and dates on the server side (Ruby) before sending to the client

Files:

  • app/models/simplefin_account.rb
  • app/models/recurring_transaction.rb
  • app/helpers/simplefin_items_helper.rb
  • app/models/simplefin_account/processor.rb
app/{models,controllers,views}/**/*.{rb,erb}

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

Avoid N+1 queries

Files:

  • app/models/simplefin_account.rb
  • app/models/recurring_transaction.rb
  • app/controllers/simplefin_items_controller.rb
  • app/views/simplefin_items/relink.html.erb
  • app/models/simplefin_account/processor.rb
test/**/*_test.rb

📄 CodeRabbit inference engine (AGENTS.md)

Name Minitest files with *_test.rb and mirror the app/ structure under test/

test/**/*_test.rb: ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.
Use Mocha for mocking in tests when necessary.
Use VCR for external API tests.

test/**/*_test.rb: Always use Minitest for tests; do not use RSpec
Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead
For tests needing many records, use Rails helpers to construct data and inline the creation in the test
Only write tests for critical and important code paths
Avoid tests that merely verify framework/ActiveRecord behavior
Test boundaries correctly: for queries, assert returned values; for commands, assert collaborators are called with correct params
Never test the implementation details of one class in another class’s test suite
Use the mocha gem for stubs and mocks
Prefer OpenStruct for mock instances; use a mock class for complex cases
Only mock what’s necessary; don’t mock return values unless they are under test

Files:

  • test/views/transactions/merged_badge_view_test.rb
app/views/**/*.erb

📄 CodeRabbit inference engine (AGENTS.md)

app/views/**/*.erb: Keep heavy logic out of ERB views; prefer helpers/components instead
ERB templates are linted by erb-lint per .erb_lint.yml

Always use the icon helper (icon) for icons; never call lucide_icon directly

Files:

  • app/views/simplefin_items/relink.html.erb
app/views/**/*.html.*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/views/**/*.html.*: Use partials only for simple, context-specific, mostly static HTML content.
Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.
Use query params for state, not localStorage or sessionStorage.
Always perform server-side formatting for currencies, numbers, and dates.

Files:

  • app/views/simplefin_items/relink.html.erb
**/*.{html,erb,slim,js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Always use functional design tokens (e.g., text-primary, bg-container) from the design system; do not use raw colors or ad-hoc classes.

Files:

  • app/views/simplefin_items/relink.html.erb
app/views/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/project-conventions.mdc)

app/views/**/*.html.erb: Prefer native HTML elements (e.g., ,

) over JS-based components
Leverage Turbo frames to break up pages instead of JS-driven client-side solutions
Prefer native client-side form validation when possible

Files:

  • app/views/simplefin_items/relink.html.erb
{app/javascript/controllers/**/*.{js,ts},app/views/**/*.erb,app/components/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Use declarative Stimulus actions in ERB (data-action) instead of imperative event listeners in controller lifecycle (e.g., avoid addEventListener in connect); controllers should just respond to actions

Files:

  • app/views/simplefin_items/relink.html.erb
{app/components/**/*.{js,ts,erb},app/views/**/*.erb}

📄 CodeRabbit inference engine (.cursor/rules/stimulus_conventions.mdc)

Component-scoped Stimulus controllers in app/components must be used only within their component views, not in app/views

Files:

  • app/views/simplefin_items/relink.html.erb
{app/views/**,app/helpers/**,app/javascript/controllers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

{app/views/**,app/helpers/**,app/javascript/controllers/**}: Style UI using TailwindCSS v4.x with the custom design system defined in app/assets/tailwind/maybe-design-system.css
Always start by referencing app/assets/tailwind/maybe-design-system.css to understand available primitives, functional tokens, and component tokens before styling
Prefer functional tokens from the design system over raw Tailwind values (e.g., use text-primary, bg-container, border border-primary instead of text-white, bg-white, border-gray-200)

Files:

  • app/views/simplefin_items/relink.html.erb
  • app/helpers/simplefin_items_helper.rb
{app/views/**,app/helpers/**}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • app/views/simplefin_items/relink.html.erb
  • app/helpers/simplefin_items_helper.rb
{app/views,app/components}/**/*.html.erb

📄 CodeRabbit inference engine (.cursor/rules/view_conventions.mdc)

{app/views,app/components}/**/*.html.erb: Keep domain logic out of ERB templates; compute values in component/controller code and reference them in the template
Integrate Stimulus declaratively in ERB: templates declare data-controller/data-action; controllers respond to those declarations
Pass data from Rails to Stimulus via data-*-value attributes, not inline JavaScript

Files:

  • app/views/simplefin_items/relink.html.erb
🧠 Learnings (32)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to db/migrate/**/*.rb : Enforce simple validations in the database (NULL constraints, unique indexes, etc.)

Applied to files:

  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
  • app/models/simplefin_account.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to db/migrate/*.rb : Simple validations like null checks and unique constraints should be handled by database constraints in migration files.

Applied to files:

  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/models/simplefin_account.rb
  • lib/tasks/simplefin_backfill.rake
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/models/simplefin_account.rb
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account.rb : Account syncs are orchestrated by Account#sync_data (auto-match transfers, compute balances from start_date..today, enrich transactions) and should run whenever an Entry is updated

Applied to files:

  • app/models/simplefin_account.rb
  • lib/tasks/simplefin_backfill.rake
  • lib/tasks/was_merged.rake
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account/processor.rb
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.

Applied to files:

  • app/models/simplefin_account.rb
  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/models/simplefin_account.rb
  • lib/tasks/simplefin_backfill.rake
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account/processor.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.

Applied to files:

  • app/models/simplefin_account.rb
  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/models/simplefin_account.rb
  • lib/tasks/simplefin_backfill.rake
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account/processor.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Avoid tests that merely verify framework/ActiveRecord behavior

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Respect existing tests and add tests when changing critical logic.

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : Do not use factories (e.g., FactoryBot) in tests; rely on fixtures instead

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases; create edge cases inline in tests

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/**/*_test.rb : ALWAYS use Minitest + fixtures + Mocha for tests; NEVER RSpec or FactoryBot.

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/**/*_test.rb : For tests needing many records, use Rails helpers to construct data and inline the creation in the test

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to test/fixtures/**/*.yml : Keep fixtures minimal: 2–3 per model for base cases.

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-23T22:22:48.511Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-23T22:22:48.511Z
Learning: Applies to test/fixtures/**/*.yml : Use fixtures for test data rather than factories

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to test/fixtures/**/* : Use test/fixtures for fixtures (and existing VCR cassettes if present)

Applied to files:

  • test/views/transactions/merged_badge_view_test.rb
📚 Learning: 2025-09-13T11:40:26.913Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/models/account/{balance_calculator.rb,balance/base_calculator.rb} : Calculate account balances daily using Account::BalanceCalculator and Account::Balance::BaseCalculator

Applied to files:

  • lib/tasks/was_merged.rake
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account/processor.rb
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:15:56.406Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-design.mdc:0-0
Timestamp: 2025-09-16T13:15:56.406Z
Learning: Applies to app/controllers/concerns/auto_sync.rb : Run daily Family syncs via the AutoSync concern; Family sync orchestrates Account and Plaid Item syncs

Applied to files:

  • app/controllers/simplefin_items_controller.rb
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/{models,controllers,views}/**/*.{rb,erb} : Avoid N+1 queries

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account/processor.rb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/**/*.html.erb : Leverage Turbo frames to break up pages instead of JS-driven client-side solutions

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to app/views/**/*.html.* : Prefer semantic HTML; use Turbo Frames where possible instead of client-side solutions.

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-23T22:21:06.637Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/project-conventions.mdc:0-0
Timestamp: 2025-09-23T22:21:06.637Z
Learning: Applies to app/views/layouts/application.html.erb : Use Turbo frames in the application layout to load controller actions as demonstrated

Applied to files:

  • app/views/simplefin_items/relink.html.erb
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: Applies to **/*.rb : Ruby style: 2-space indentation; snake_case for methods/variables; CamelCase for classes/modules

Applied to files:

  • app/helpers/simplefin_items_helper.rb
📚 Learning: 2025-09-20T08:37:48.022Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T08:37:48.022Z
Learning: Applies to **/*.{rb,js,jsx,ts,tsx} : Make changes atomic, testable, and explain their impact briefly in code suggestions.

Applied to files:

  • app/helpers/simplefin_items_helper.rb
  • lib/tasks/simplefin_fix_was_merged.rake
📚 Learning: 2025-09-16T13:17:53.155Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-16T13:17:53.155Z
Learning: PRs should include clear description, linked issues, screenshots for UI changes, and migration notes; ensure CI, tests, RuboCop, and Biome are clean

Applied to files:

  • app/helpers/simplefin_items_helper.rb
  • lib/tasks/simplefin_fix_was_merged.rake
🧬 Code graph analysis (5)
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)
db/migrate/20251028104241_fix_account_providers_indexes.rb (1)
  • change (1-18)
app/models/simplefin_account.rb (1)
app/models/account/linkable.rb (1)
  • provider (27-31)
app/controllers/simplefin_items_controller.rb (6)
app/models/simplefin_item.rb (2)
  • include (1-301)
  • dedup_simplefin_accounts! (163-226)
app/controllers/accounts_controller.rb (3)
  • index (5-35)
  • family (101-103)
  • sync (53-69)
app/controllers/concerns/simplefin_items/relink_helpers.rb (2)
  • compute_unlinked_count (10-15)
  • compute_relink_candidates (23-114)
app/models/simplefin_item/relink_service.rb (2)
  • new (3-122)
  • apply! (14-121)
app/services/simplefin_item/unlinker.rb (1)
  • unlink_all! (11-32)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/helpers/simplefin_items_helper.rb (1)
app/models/simplefin_item/importer.rb (1)
  • stats (103-105)
app/models/simplefin_account/processor.rb (2)
app/models/simplefin_account.rb (2)
  • current_account (16-18)
  • ensure_account_provider! (22-35)
app/models/plaid_account/processor.rb (1)
  • process_account! (31-95)
🔇 Additional comments (15)
db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)

12-17: LGTM!

The down method correctly handles reversibility by recreating the legacy index with proper existence guards. While it recreates a duplicate, this is the correct behavior for rollback—restoring the database to its previous state.

app/models/recurring_transaction.rb (1)

47-47: LGTM! Date comparison fix is correct.

The .to_date conversion ensures Date-to-Date comparison and prevents the ArgumentError that was flagged in the previous review.

test/views/transactions/merged_badge_view_test.rb (1)

9-17: LGTM!

Inline creation of the Transaction and Entry with was_merged: true is appropriate here, as this is an edge case being tested. The Entry has all required fields properly set.

lib/tasks/simplefin_backfill.rake (4)

23-56: LGTM: Argument parsing and validation are solid.

The flexible argument parsing (positional and key=value), UUID validation, and safe defaults (dry_run=true by default) are well-implemented.


100-132: Date parsing is robust with intentional silent failure handling.

The multi-type date parsing (String, Numeric, Date, Time, DateTime) with rescue nil at lines 103 and 116 ensures the task doesn't crash on malformed data. Transactions with unparseable dates are safely skipped at lines 129-132.


136-148: LGTM: Extra payload construction is correct.

Using t.key?(:payee) (lines 138-141) correctly distinguishes between absent keys and nil values, and the force flag (line 145) appropriately controls re-processing behavior.


180-224: LGTM: Cleanup task is correctly implemented.

The dry_run default (line 193) correctly defaults to true when blank, addressing the previous review concern. The parameterized LIKE queries (lines 205-207) prevent SQL injection, and the transaction wrapping (lines 214-219) ensures atomic deletions.

app/helpers/simplefin_items_helper.rb (1)

15-36: LGTM! Well-structured tooltip helper.

The implementation is clean and correct. The early nil return for zero errors, guard against non-hash input, and compact formatting with sample errors and bucket counts all work well together. The past review concerns have been properly addressed.

app/controllers/simplefin_items_controller.rb (7)

23-108: LGTM! Update action is well-structured.

The action properly handles token updates with async balances refresh (line 60), safe account transfer in a transaction (lines 39-54), relink candidate detection, and comprehensive error handling with appropriate flash messages. The past review concerns about synchronous API calls and flash messaging have been addressed.


114-169: LGTM! Create action follows best practices.

The action uses async job dispatch (line 126), consistent helper usage for unlinked count computation (lines 130-138), and intelligent relink candidate detection (lines 140-154). Error handling is comprehensive with clear user messaging.


194-203: Verify that synchronous SimpleFin API call is intentional.

Line 197 calls perform_sync synchronously in the request thread, which blocks until the SimpleFin API responds. In contrast, the create and update actions use SimplefinItem::BalancesOnlyJob.perform_later for async execution (lines 60, 126). A past review flagged this as a reliability concern.

If balances-only syncs are guaranteed to be fast and lightweight, document this decision with a comment explaining why synchronous execution is safe. Otherwise, align with the async pattern used elsewhere.

Consider this async alternative:

   def balances
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+    unless @simplefin_item.syncing?
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItem::BalancesOnlyJob.perform_later(@simplefin_item.id)
+    end
 
     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

205-256: LGTM! Setup accounts action is well-designed.

The action safely dedups (lines 207-211), properly fetches unlinked accounts with explicit joins to avoid N+1 (lines 213-217), and includes helpful logging for observability (lines 219-221). The account type and subtype options are clearly structured.


292-308: LGTM! Errors action handles data safely.

The action defensively extracts sync stats (line 295), handles both symbol and string keys in error data (lines 298-304), and properly renders without layout for modal display (line 307).


310-361: LGTM! Relink actions are well-implemented.

The relink action (lines 311-328) safely computes candidates and prepares fallback lists with proper joins. The apply_relink action (lines 337-361) correctly filters checked pairs (line 338), delegates to the service layer with family scoping (lines 341-345), and rebuilds maps before rendering (line 348) to avoid N+1 queries. The multi-format response with Turbo Streams (lines 351-356) enables seamless live updates. Past FK violation concerns have been addressed.


389-425: LGTM! Excellent N+1 query prevention.

This helper is well-designed to precompute data for the view. The syncs.loaded? check (lines 400-404) intelligently avoids redundant queries when syncs are eager-loaded via includes(:syncs). The three maps (sync_stats, has_unlinked, unlinked_count) are populated with proper left joins and defensive fallbacks (lines 421-424).

Based on learnings.

jjmata and others added 3 commits November 15, 2025 19:59
…nify \"manual\" scope, and correct unlinked counts

- Preserve #manual-accounts wrapper: switch non-empty updates to turbo_stream.update and background broadcast_update_to; keep empty-path replace to render <div id=\"manual-accounts\"></div>
- Unify definition of manual accounts via Account.visible_manual (visible + legacy-nil + no AccountProvider); reuse in controllers, jobs, and helper
- Correct setup/unlinked counts: SimplefinItem::Syncer#finalize_setup_counts and maps now consider AccountProvider links (legacy account AND provider must be absent)
Deleted:
- app/models/simplefin_item/relink_service.rb
- app/controllers/concerns/simplefin_items/relink_helpers.rb
- app/javascript/controllers/auto_relink_controller.js
- app/views/simplefin_items/_relink_modal.html.erb
- app/views/simplefin_items/manual_relink.html.erb
- app/views/simplefin_items/relink.html.erb
- test/services/simplefin_item/relink_service_test.rb
Refs: PR we-promise#318 unified link/unlink; PR we-promise#267 SimpleFIN; follow-up to fix wrapper ID loss and counting drift."
@luckyPipewrench luckyPipewrench force-pushed the simplefin-enhancements-v2 branch from 2b7b1f9 to 297670a Compare November 16, 2025 01:00
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/models/simplefin_item/importer.rb (1)

171-174: Use chunk_size_days for the guard, not 365.

Your chunks target 60 days; enforcing 365 here is inconsistent and risks oversize requests if math drifts.

-        if actual_days > 365
-          Rails.logger.error "SimpleFin: Chunk exceeds 365 days (#{actual_days} days). This should not happen."
-          chunk_start_date = chunk_end_date - 365.days
+        if actual_days > chunk_size_days
+          Rails.logger.error "SimpleFin: Chunk exceeds #{chunk_size_days} days (#{actual_days} days). Adjusting."
+          chunk_start_date = chunk_end_date - chunk_size_days.days
app/models/simplefin_item/syncer.rb (1)

27-48: Wrap full-sync in rescue and call mark_failed on exceptions.

Balances-only path handles failures; full path doesn’t, so job/request can crash without status updates.

-    # Full sync path
-    sync.update!(status_text: "Importing accounts from SimpleFin...") if sync.respond_to?(:status_text)
-    simplefin_item.import_latest_simplefin_data(sync: sync)
-
-    finalize_setup_counts(sync)
+    # Full sync path
+    sync.update!(status_text: "Importing accounts from SimpleFin...") if sync.respond_to?(:status_text)
+    begin
+      simplefin_item.import_latest_simplefin_data(sync: sync)
+      finalize_setup_counts(sync)
+    rescue => e
+      mark_failed(sync, e)
+      return
+    end
♻️ Duplicate comments (8)
lib/tasks/holdings_tools.rake (1)

68-74: Fix error message to match parameter name.

The task parameter is :snapshot_id (line 69) and the description says "Args: snapshot_id" (line 68), but the error message on line 72 says "Provide id". This inconsistency could confuse users.

Apply this diff:

-        puts({ ok: false, error: "usage", message: "Provide id" }.to_json)
+        puts({ ok: false, error: "usage", message: "Provide snapshot_id" }.to_json)
db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1)

3-3: Migration version fixed.

The migration now correctly inherits from ActiveRecord::Migration[7.2], consistent with project guidelines.

app/controllers/accounts_controller.rb (1)

13-51: Eliminate N+1 queries and code duplication by delegating to the shared helper.

This map-building logic causes N+1 queries:

  • Lines 20-22: queries item.family.accounts.visible_manual.exists? per item
  • Lines 28-31: queries unlinked count per item
  • Lines 43-44: may query simplefin_accounts.exists? per item if not loaded

Additionally, this duplicates the batched logic already implemented in SimplefinItems::MapsHelper.build_simplefin_maps_for (app/controllers/concerns/simplefin_items/maps_helper.rb).

Line 47 also uses a bare rescue which should be rescue StandardError.

Apply this refactor to eliminate both issues:

 class AccountsController < ApplicationController
+  include SimplefinItems::MapsHelper
   before_action :set_account, only: %i[sync sparkline toggle_active show destroy unlink confirm_unlink select_provider]
   include Periodable

   def index
     @manual_accounts = family.accounts
           .visible_manual
           .order(:name)
     @plaid_items = family.plaid_items.ordered
     @simplefin_items = family.simplefin_items.ordered.includes(:syncs)
     @lunchflow_items = family.lunchflow_items.ordered

     # Precompute per-item maps to avoid queries in the view
-    @simplefin_sync_stats_map = {}
-    @simplefin_has_unlinked_map = {}
-
-    @simplefin_items.each do |item|
-      latest_sync = item.syncs.ordered.first
-      @simplefin_sync_stats_map[item.id] = (latest_sync&.sync_stats || {})
-      @simplefin_has_unlinked_map[item.id] = item.family.accounts
-        .visible_manual
-        .exists?
-    end
-
-    # Count of SimpleFin accounts that are not linked (no legacy account and no AccountProvider)
-    @simplefin_unlinked_count_map = {}
-    @simplefin_items.each do |item|
-      count = item.simplefin_accounts
-        .left_joins(:account, :account_provider)
-        .where(accounts: { id: nil }, account_providers: { id: nil })
-        .count
-      @simplefin_unlinked_count_map[item.id] = count
-    end
-
-    # Compute CTA visibility map used by the simplefin_item partial
-    @simplefin_show_relink_map = {}
-    @simplefin_items.each do |item|
-      begin
-        unlinked_count = @simplefin_unlinked_count_map[item.id] || 0
-        manuals_exist = @simplefin_has_unlinked_map[item.id]
-        sfa_any = if item.simplefin_accounts.loaded?
-          item.simplefin_accounts.any?
-        else
-          item.simplefin_accounts.exists?
-        end
-        @simplefin_show_relink_map[item.id] = (unlinked_count.to_i == 0 && manuals_exist && sfa_any)
-      rescue => e
-        Rails.logger.warn("SimpleFin card: CTA computation failed for item #{item.id}: #{e.class} - #{e.message}")
-        @simplefin_show_relink_map[item.id] = false
-      end
-    end
+    build_simplefin_maps_for(@simplefin_items)

     # Prevent Turbo Drive from caching this page to ensure fresh account lists
     expires_now
app/controllers/settings/bank_sync_controller.rb (1)

23-23: Use production SimpleFin endpoint, not beta.

The URL https://beta-bridge.simplefin.org points to a beta/demo environment. According to previous review findings and SimpleFin documentation, the production endpoint is https://bridge.simplefin.org/simplefin. Please update to use the production URL.

lib/tasks/simplefin_holdings_backfill.rake (1)

99-102: Critical: Undefined variable reference.

Line 99 references errors.empty? before errors is initialized on line 106. This will raise a NameError when this branch executes (i.e., when no selector argument is provided).

Apply this diff to fix:

-      success = errors.empty?
       puts({ ok: false, error: "usage", message: "Provide one of item_id, account_id, or account_name" }.to_json)
       exit 1
app/models/simplefin_account/investments/holdings_processor.rb (1)

50-52: Fix holding date lookup to include created_at/updated. Missing keys re-date holdings to today.

Not checking underscored keys can corrupt historical valuations on import. Add both variants.

-        # Use best-known date: created -> updated_at -> as_of -> date -> today
-        holding_date = parse_holding_date(any_of(simplefin_holding, %w[created updated_at as_of date])) || Date.current
+        # Use best-known date: created/created_at -> updated/updated_at -> as_of -> date -> today
+        holding_date = parse_holding_date(
+          any_of(simplefin_holding, %w[created created_at updated updated_at as_of date])
+        ) || Date.current

Based on learnings

lib/tasks/simplefin_backfill.rake (1)

170-181: Guard against undefined composite_match method to prevent crash in dry-run.

The adapter.composite_match(...) call at line 175 will raise NoMethodError because this method is not defined in Account::ProviderImportAdapter. Add a guard or fallback:

-              entry ||= adapter.composite_match(
-                source: "simplefin",
-                name: processor.send(:name),
-                amount: processor.send(:amount),
-                date: (posted_d || trans_d),
-                window_days: window_days
-              )
+              if adapter.respond_to?(:composite_match)
+                entry ||= adapter.composite_match(
+                  source: "simplefin",
+                  name: processor.send(:name),
+                  amount: processor.send(:amount),
+                  date: (posted_d || trans_d),
+                  window_days: window_days
+                )
+              else
+                # Fallback: match by external_id or local query
+                entry ||= acct.entries.where(
+                  source: "simplefin",
+                  name: processor.send(:name),
+                  amount: processor.send(:amount),
+                  date: (posted_d || trans_d)
+                ).first
+              end
app/controllers/simplefin_items_controller.rb (1)

143-152: Remove synchronous sync call; queue async job instead.

The current code calls perform_sync synchronously (line 147), blocking the request. However, the review's suggested job name is incorrect—SimplefinItemSyncJob doesn't exist.

Use one of two correct patterns:

  • Simpler: Queue SimplefinItem::BalancesOnlyJob.perform_later(@simplefin_item.id) directly (purpose-built for this use case)
  • Alternative: Create the sync and queue SyncJob.perform_later(sync) instead of calling perform_sync directly

The existing sync action (line 132–136) already demonstrates the correct async pattern with sync_later.

🧹 Nitpick comments (12)
app/views/layouts/settings.html.erb (1)

7-7: Clarify if element-level cache control is redundant.

Since settings.html.erb renders _htmldoc.html.erb (line 1), which now includes a document-level turbo-cache-control: no-cache meta tag, this element-level data-turbo-cache="false" attribute may be redundant. The document-level meta tag already disables caching for the entire page.

If the document-level meta tag remains, consider removing this attribute to avoid duplication. Alternatively, if the document-level tag is moved elsewhere (see previous comment), this element-level control becomes the appropriate solution for settings pages.

db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1)

30-35: Consider clarifying the FK replacement logic.

The pattern of checking existence, removing, then adding with an unless foreign_key_exists? guard works correctly but may be unclear to future maintainers. The guard is defensive (prevents duplicate FK errors if migration runs multiple times), but after the removal on line 33, the FK won't exist, making the guard mostly redundant.

Consider adding a comment or simplifying to:

# Entries -> Accounts (account_id) with CASCADE
if foreign_key_exists?(:entries, :accounts)
  remove_foreign_key :entries, :accounts
end
# Add with CASCADE (guard prevents duplicate if FK somehow still exists)
add_foreign_key :entries, :accounts, column: :account_id, on_delete: :cascade unless foreign_key_exists?(:entries, :accounts)

Or simplify by removing the guard if Rails migration tracking is trusted:

# Entries -> Accounts (account_id) with CASCADE
remove_foreign_key :entries, :accounts if foreign_key_exists?(:entries, :accounts)
add_foreign_key :entries, :accounts, column: :account_id, on_delete: :cascade
app/jobs/simplefin_item/balances_only_job.rb (3)

22-27: Replace bare rescue with rescue StandardError.

The bare rescue at line 25 catches all exceptions, including system-level ones like SignalException and SystemExit, which can interfere with graceful shutdowns.

Apply this diff:

     begin
       item.update!(last_synced_at: Time.current) if item.has_attribute?(:last_synced_at)
-    rescue => e
+    rescue StandardError => e
       Rails.logger.warn("SimpleFin BalancesOnlyJob last_synced_at update failed: #{e.class} - #{e.message}")
     end

39-57: Replace bare rescue with rescue StandardError.

The bare rescue at line 55 catches system-level exceptions. Use StandardError instead.

Apply this diff:

           Turbo::StreamsChannel.broadcast_replace_to(item.family, target: "manual-accounts", html: manual_html)
         end
-      rescue => inner
+      rescue StandardError => inner
         Rails.logger.warn("SimpleFin BalancesOnlyJob manual-accounts broadcast failed: #{inner.class} - #{inner.message}")
       end

29-60: Replace bare rescue with rescue StandardError.

The bare rescue at line 58 catches system-level exceptions. Use StandardError instead.

Apply this diff:

         Rails.logger.warn("SimpleFin BalancesOnlyJob manual-accounts broadcast failed: #{inner.class} - #{inner.message}")
       end
-    rescue => e
+    rescue StandardError => e
       Rails.logger.warn("SimpleFin BalancesOnlyJob broadcast failed: #{e.class} - #{e.message}")
     end
lib/tasks/simplefin_debug.rake (1)

19-26: Consider database-level filtering to avoid loading all accounts.

The current approach loads all simplefin_accounts into memory and filters in Ruby. For items with many accounts, this is inefficient. Consider using a database query similar to the pattern at lines 74-77.

Apply this diff if you want to optimize:

-      sfa = item.simplefin_accounts.order(updated_at: :desc).find do |acc|
-        acc.name.to_s.downcase.include?(args[:account_name].to_s.downcase)
-      end
+      sfa = item.simplefin_accounts
+        .where("LOWER(name) LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(args[:account_name].to_s.downcase)}%")
+        .order(updated_at: :desc)
+        .first

Note: Since this is a debug task rather than a production code path, the current implementation is acceptable.

app/controllers/concerns/simplefin_items/maps_helper.rb (1)

80-92: Replace bare rescue with rescue StandardError.

Line 90 uses a bare rescue which catches system-level exceptions. This is inconsistent with the fix at line 66 and can mask critical errors.

Apply this diff:

       errs.present? && errs.all? { |m| m.to_s.downcase.include?("duplicate upstream account detected") }
-    rescue
+    rescue StandardError
       false
     end
test/models/simplefin_item/importer_duplicate_test.rb (2)

17-19: Consider more targeted stubbing to avoid test pollution.

Using SimplefinAccount.any_instance.stubs(:save!) affects all SimplefinAccount instances globally, which can cause test pollution and make the test brittle. The .then.returns(true) chain suggests the test expects multiple save! calls, but it's unclear if this matches the actual behavior.

Consider stubbing at a more specific scope or using expectations that are clearer about call counts.


35-36: Same global stubbing concern applies here.

Similar to the first test, the global any_instance.stubs(:save!) pattern can affect other parts of the test suite if tests run in parallel or if there's any shared state.

app/views/simplefin_items/_simplefin_item.html.erb (2)

45-53: Extract “time-ago” parsing to a small helper to DRY the view.

You compute ago = time_ago_in_words(Time.parse(ts)) twice with identical rescue. Move into a helper like simplefin_time_ago(ts) for clarity and testability.

Also applies to: 195-199


212-226: Consider removing the local fallback query when maps are present.

The fallback left_joins query is correct but can reintroduce N+1 under broadcast-only renders. If controller maps are reliably provided, prefer the map and log when missing; skip the DB fallback.

app/models/simplefin_account/investments/holdings_processor.rb (1)

114-121: Make parse_holding_date accept Time and Date.

Payloads can already be Time/Date. Handle them directly.

   case created_timestamp
   when Integer
     Time.at(created_timestamp).to_date
   when String
     Date.parse(created_timestamp)
+  when Time, Date
+    created_timestamp.to_date
   else
     nil
   end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36b1da9 and 297670a.

📒 Files selected for processing (70)
  • app/controllers/accounts_controller.rb (1 hunks)
  • app/controllers/concerns/simplefin_items/maps_helper.rb (1 hunks)
  • app/controllers/settings/bank_sync_controller.rb (1 hunks)
  • app/controllers/settings/providers_controller.rb (3 hunks)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/helpers/settings_helper.rb (1 hunks)
  • app/helpers/simplefin_items_helper.rb (1 hunks)
  • app/helpers/transactions_helper.rb (1 hunks)
  • app/jobs/simplefin_item/balances_only_job.rb (1 hunks)
  • app/models/account.rb (2 hunks)
  • app/models/account/provider_import_adapter.rb (2 hunks)
  • app/models/family/auto_transfer_matchable.rb (1 hunks)
  • app/models/holding.rb (1 hunks)
  • app/models/recurring_transaction/identifier.rb (1 hunks)
  • app/models/simplefin_account.rb (1 hunks)
  • app/models/simplefin_account/investments/holdings_processor.rb (4 hunks)
  • app/models/simplefin_account/processor.rb (1 hunks)
  • app/models/simplefin_entry/processor.rb (3 hunks)
  • app/models/simplefin_item.rb (4 hunks)
  • app/models/simplefin_item/importer.rb (9 hunks)
  • app/models/simplefin_item/syncer.rb (2 hunks)
  • app/models/simplefin_item/unlinking.rb (1 hunks)
  • app/services/simplefin_item/unlinker.rb (1 hunks)
  • app/views/accounts/_account.html.erb (1 hunks)
  • app/views/accounts/index.html.erb (1 hunks)
  • app/views/accounts/index/_account_groups.erb (1 hunks)
  • app/views/holdings/_holding.html.erb (1 hunks)
  • app/views/layouts/settings.html.erb (1 hunks)
  • app/views/layouts/shared/_htmldoc.html.erb (1 hunks)
  • app/views/settings/_settings_nav.html.erb (1 hunks)
  • app/views/settings/providers/_provider_form.html.erb (1 hunks)
  • app/views/settings/providers/_simplefin_panel.html.erb (1 hunks)
  • app/views/settings/providers/show.html.erb (1 hunks)
  • app/views/simplefin_items/_simplefin_item.html.erb (4 hunks)
  • app/views/simplefin_items/edit.html.erb (2 hunks)
  • app/views/simplefin_items/errors.html.erb (1 hunks)
  • app/views/simplefin_items/index.html.erb (0 hunks)
  • app/views/simplefin_items/new.html.erb (1 hunks)
  • app/views/simplefin_items/select_existing_account.html.erb (1 hunks)
  • app/views/simplefin_items/setup_accounts.html.erb (1 hunks)
  • app/views/simplefin_items/show.html.erb (0 hunks)
  • app/views/transactions/show.html.erb (1 hunks)
  • config/locales/views/simplefin_items/update.en.yml (1 hunks)
  • config/routes.rb (1 hunks)
  • db/migrate/20251029190000_add_extra_to_transactions.rb (1 hunks)
  • db/migrate/20251030172500_add_cascade_on_account_deletes.rb (1 hunks)
  • db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1 hunks)
  • db/migrate/20251103185320_drop_was_merged_from_transactions.rb (1 hunks)
  • db/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rb (1 hunks)
  • db/migrate/20251115194500_allow_null_merchant_id_on_recurring_transactions.rb (1 hunks)
  • db/schema.rb (5 hunks)
  • lib/simplefin/date_utils.rb (1 hunks)
  • lib/tasks/holdings_tools.rake (1 hunks)
  • lib/tasks/simplefin.rake (1 hunks)
  • lib/tasks/simplefin_backfill.rake (1 hunks)
  • lib/tasks/simplefin_debug.rake (1 hunks)
  • lib/tasks/simplefin_fix_was_merged.rake (1 hunks)
  • lib/tasks/simplefin_holdings_backfill.rake (1 hunks)
  • lib/tasks/simplefin_unlink.rake (1 hunks)
  • test/controllers/accounts_controller_test.rb (1 hunks)
  • test/controllers/settings/hostings_controller_test.rb (1 hunks)
  • test/controllers/settings/providers_controller_test.rb (1 hunks)
  • test/controllers/simplefin_items_controller_test.rb (6 hunks)
  • test/models/account/provider_import_adapter_test.rb (5 hunks)
  • test/models/recurring_transaction_test.rb (2 hunks)
  • test/models/simplefin_entry/processor_test.rb (1 hunks)
  • test/models/simplefin_item/importer_duplicate_test.rb (1 hunks)
  • test/services/simplefin_item/unlinker_test.rb (1 hunks)
  • test/system/settings/ai_prompts_test.rb (0 hunks)
  • test/views/transactions/merged_badge_view_test.rb (1 hunks)
💤 Files with no reviewable changes (3)
  • test/system/settings/ai_prompts_test.rb
  • app/views/simplefin_items/index.html.erb
  • app/views/simplefin_items/show.html.erb
✅ Files skipped from review due to trivial changes (2)
  • app/views/settings/providers/_provider_form.html.erb
  • lib/tasks/simplefin_fix_was_merged.rake
🚧 Files skipped from review as they are similar to previous changes (22)
  • app/models/holding.rb
  • app/views/simplefin_items/edit.html.erb
  • app/views/accounts/index/_account_groups.erb
  • app/models/family/auto_transfer_matchable.rb
  • config/locales/views/simplefin_items/update.en.yml
  • app/views/accounts/index.html.erb
  • db/migrate/20251103185320_drop_was_merged_from_transactions.rb
  • test/controllers/accounts_controller_test.rb
  • test/services/simplefin_item/unlinker_test.rb
  • test/controllers/settings/hostings_controller_test.rb
  • app/helpers/settings_helper.rb
  • app/models/simplefin_item/unlinking.rb
  • app/views/holdings/_holding.html.erb
  • app/views/settings/_settings_nav.html.erb
  • app/models/simplefin_entry/processor.rb
  • db/migrate/20251029190000_add_extra_to_transactions.rb
  • app/views/simplefin_items/setup_accounts.html.erb
  • app/controllers/settings/providers_controller.rb
  • app/views/simplefin_items/errors.html.erb
  • test/views/transactions/merged_badge_view_test.rb
  • app/helpers/simplefin_items_helper.rb
  • config/routes.rb
🧰 Additional context used
🧠 Learnings (19)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Pass data from Rails to Stimulus using data-*-value attributes, not inline JavaScript

Applied to files:

  • app/views/layouts/settings.html.erb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • test/models/recurring_transaction_test.rb
  • lib/tasks/simplefin_backfill.rake
  • test/models/account/provider_import_adapter_test.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/models/simplefin_account/processor.rb
  • app/models/account/provider_import_adapter.rb
  • app/jobs/simplefin_item/balances_only_job.rb
  • app/models/simplefin_item/syncer.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • test/models/simplefin_entry/processor_test.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account.rb
  • db/schema.rb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/views/settings/providers/_simplefin_panel.html.erb
  • app/controllers/simplefin_items_controller.rb
  • app/views/simplefin_items/new.html.erb
📚 Learning: 2025-08-22T18:30:26.758Z
Learnt from: jjmata
Repo: we-promise/sure PR: 116
File: app/views/family_exports/_list.html.erb:16-28
Timestamp: 2025-08-22T18:30:26.758Z
Learning: In the family exports UI (app/views/family_exports/_list.html.erb), raw color utilities like bg-green-500/5, text-green-500, bg-red-500/5, text-red-500 are preferred over design system tokens for status indicators to maintain UI legibility and consistency with the existing import UI patterns. The user has indicated that design token replacements would make the UI illegible in this context.

Applied to files:

  • app/views/settings/providers/_simplefin_panel.html.erb
📚 Learning: 2025-10-24T12:37:31.878Z
Learnt from: jjmata
Repo: we-promise/sure PR: 77
File: app/assets/tailwind/google-sign-in.css:1-106
Timestamp: 2025-10-24T12:37:31.878Z
Learning: In app/assets/tailwind/google-sign-in.css: Google OAuth button styles (gsi-material-button classes) should be kept with their original hardcoded colors and fonts as provided by Google, even though they don't use design system tokens. This is a brand compliance exception.

Applied to files:

  • app/views/settings/providers/_simplefin_panel.html.erb
📚 Learning: 2025-09-13T11:40:26.913Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • test/models/account/provider_import_adapter_test.rb
  • app/models/simplefin_account/processor.rb
  • app/jobs/simplefin_item/balances_only_job.rb
  • app/models/simplefin_item/syncer.rb
  • app/models/simplefin_account/investments/holdings_processor.rb
  • test/models/simplefin_entry/processor_test.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account.rb
  • db/schema.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/models/simplefin_item/importer.rb
  • app/models/simplefin_account.rb
📚 Learning: 2025-08-22T23:28:38.143Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/models/simplefin_account.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • test/controllers/simplefin_items_controller_test.rb
  • app/views/simplefin_items/_simplefin_item.html.erb
  • test/models/simplefin_item/importer_duplicate_test.rb
  • app/models/simplefin_item/importer.rb
  • app/controllers/concerns/simplefin_items/maps_helper.rb
  • db/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rb
  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account.rb
  • app/models/simplefin_item.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • lib/tasks/simplefin_backfill.rake
  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/models/account/provider_import_adapter.rb
  • app/models/simplefin_item/importer.rb
  • app/controllers/accounts_controller.rb
  • app/controllers/concerns/simplefin_items/maps_helper.rb
  • app/controllers/simplefin_items_controller.rb
  • db/schema.rb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Keep domain logic out of ERB templates; compute classes and logic in component/Ruby code, not inline in the template

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
  • app/views/transactions/show.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use partials when content is primarily static, simple, context-specific, and without variants or complex configuration

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/views/**/*.html.erb : Use ViewComponents when elements have complex logic/styling, are reused across contexts, need variants/slots/accessibility, or require Stimulus interactivity

Applied to files:

  • app/views/simplefin_items/_simplefin_item.html.erb
📚 Learning: 2025-09-03T01:15:59.638Z
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.

Applied to files:

  • app/models/simplefin_account/processor.rb
  • app/models/simplefin_item/importer.rb
  • app/models/simplefin_item/syncer.rb
📚 Learning: 2025-11-14T14:22:37.038Z
Learnt from: jjmata
Repo: we-promise/sure PR: 326
File: app/models/category_import.rb:10-10
Timestamp: 2025-11-14T14:22:37.038Z
Learning: In the CategoryImport model (app/models/category_import.rb), the import process implements a "last write wins" strategy where importing a category.csv file completely overrides existing category structures, including parent relationships. Setting category.parent = nil in the first pass is intentional behavior to ensure the CSV becomes the source of truth.

Applied to files:

  • app/models/simplefin_item/importer.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
  • app/models/simplefin_account.rb
  • db/schema.rb
📚 Learning: 2025-08-22T23:27:46.033Z
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: db/migrate/20250813144520_add_institution_fields_to_simplefin_items.rb:6-6
Timestamp: 2025-08-22T23:27:46.033Z
Learning: Institution URLs in financial contexts are typically short (under 100 characters) and don't require text columns - varchar(255) via :string is sufficient for SimpleFin institution URLs.

Applied to files:

  • db/schema.rb
🧬 Code graph analysis (14)
test/controllers/simplefin_items_controller_test.rb (2)
test/controllers/accounts_controller_test.rb (1)
  • fixtures (148-196)
app/models/simplefin_item.rb (1)
  • accounts (38-45)
test/models/simplefin_item/importer_duplicate_test.rb (1)
app/models/simplefin_item/importer.rb (2)
  • import_balances_only (35-68)
  • stats (116-118)
app/models/simplefin_account/processor.rb (1)
app/models/simplefin_account.rb (2)
  • current_account (16-18)
  • ensure_account_provider! (22-35)
app/models/account/provider_import_adapter.rb (3)
app/models/simplefin_entry/processor.rb (3)
  • name (59-74)
  • merchant (135-148)
  • notes (155-167)
app/models/plaid_entry/processor.rb (2)
  • name (37-39)
  • merchant (62-70)
app/models/concerns/enrichable.rb (1)
  • enrich_attribute (26-28)
app/models/simplefin_item/importer.rb (5)
app/models/simplefin_item/syncer.rb (2)
  • attr_reader (1-205)
  • initialize (4-6)
app/models/provider/simplefin.rb (4)
  • attr_reader (87-94)
  • initialize (7-8)
  • initialize (90-93)
  • get_accounts (27-74)
app/models/simplefin_item.rb (1)
  • upsert_simplefin_snapshot! (72-81)
app/models/simplefin_account.rb (1)
  • upsert_simplefin_snapshot! (37-55)
app/models/account/provider_import_adapter.rb (1)
  • update_balance (110-115)
app/controllers/accounts_controller.rb (2)
app/models/simplefin_item.rb (1)
  • accounts (38-45)
app/models/concerns/syncable.rb (1)
  • latest_sync (67-69)
app/jobs/simplefin_item/balances_only_job.rb (3)
app/models/simplefin_item/provided.rb (1)
  • simplefin_provider (4-6)
app/models/simplefin_item/importer.rb (1)
  • import_balances_only (35-68)
app/models/simplefin_item.rb (1)
  • accounts (38-45)
app/models/simplefin_item/syncer.rb (3)
app/models/simplefin_item/importer.rb (2)
  • import_balances_only (35-68)
  • stats (116-118)
app/models/simplefin_item.rb (2)
  • import_latest_simplefin_data (52-54)
  • accounts (38-45)
app/models/sync.rb (1)
  • perform_post_sync (144-151)
app/models/simplefin_account/investments/holdings_processor.rb (2)
app/models/simplefin_entry/processor.rb (7)
  • account (45-47)
  • import_adapter (41-43)
  • amount (76-93)
  • currency (95-97)
  • date (107-123)
  • external_id (53-57)
  • name (59-74)
app/models/account/provider_import_adapter.rb (1)
  • import_holding (131-193)
app/controllers/concerns/simplefin_items/maps_helper.rb (2)
app/models/concerns/syncable.rb (1)
  • latest_sync (67-69)
app/models/simplefin_item/importer.rb (1)
  • stats (116-118)
test/models/simplefin_entry/processor_test.rb (1)
app/models/simplefin_entry/processor.rb (3)
  • process (11-23)
  • name (59-74)
  • currency (95-97)
app/services/simplefin_item/unlinker.rb (1)
app/models/simplefin_item/unlinking.rb (1)
  • unlink_all! (13-56)
app/controllers/simplefin_items_controller.rb (5)
app/models/account.rb (1)
  • include (1-227)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/concerns/simplefin_items/maps_helper.rb (1)
  • build_simplefin_maps_for (9-78)
app/controllers/concerns/notifiable.rb (1)
  • flash_notification_stream_items (18-34)
app/models/simplefin_account.rb (1)
  • current_account (16-18)
app/models/simplefin_item.rb (3)
app/controllers/simplefin_items_controller.rb (3)
  • include (1-497)
  • sync (132-141)
  • new (80-82)
app/models/simplefin_item/provided.rb (1)
  • simplefin_provider (4-6)
app/models/simplefin_item/importer.rb (1)
  • import (11-32)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb

[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 346-346: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 358-358: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (46)
lib/tasks/holdings_tools.rake (4)

1-14: LGTM: Clear documentation and helpful examples.

The file header provides clear documentation with practical usage examples for both tasks, making the rake tasks easy to use.


17-28: LGTM: Flexible argument parsing with sensible defaults.

The key=value parsing pattern allows both positional and named arguments, and the default values (2% change, 1 day ago) are appropriate for demonstration purposes.


29-46: LGTM: Robust dry-run handling with safe defaults.

The dry-run logic now properly defaults to true (preview mode) when no value is provided, with strict validation of allowed values. This addresses the safety concern from previous reviews and prevents accidental data writes.


48-66: LGTM: Clean snapshot logic with proper error handling.

The snapshot creation logic is straightforward, and the error handling correctly captures and reports actual exception details. The dry-run preview output is also appropriate.

app/views/layouts/shared/_htmldoc.html.erb (1)

13-13: Verify intentional scope of app-wide cache disable.

The turbo-cache-control: no-cache meta tag in the shared _htmldoc.html.erb layout affects all pages using 7 layouts (settings, wizard, onboardings, blank, imports, auth, application). This disables Turbo Drive caching globally, causing a documented performance regression of 500–800ms per navigation.

Confirm whether this broad scope is intentional. If the goal is to ensure fresh data only for specific pages (e.g., settings or SimpleFin), consider:

  • Moving the meta tag to specific layout files (settings.html.erb, etc.)
  • Using per-element data-turbo="false" or data-turbo-temporary attributes on dynamic sections
  • Using per-page <meta name="turbo-cache-control"> in targeted layouts instead
db/migrate/20251030172500_add_cascade_on_account_deletes.rb (3)

37-51: Consistent FK replacement pattern.

The FK replacement logic for holdings and account_providers follows the same pattern as entries, maintaining consistency across the migration. The table_exists? guards appropriately handle optional schema elements.


54-70: Down method correctly reverts cascade behavior.

The rollback logic appropriately removes the ON DELETE CASCADE constraint and restores standard foreign keys. The conditional checks ensure only existing foreign keys are modified, preventing errors during rollback.


6-28: Verify destructive orphan cleanup is tested.

The migration code is well-structured with proper guards and correct SQL logic. However, no test file was found for this migration in the codebase. Confirm that the cleanup is covered by automated tests (migration specs, integration tests, or CI/CD) with production-like data volumes to ensure:

  1. Orphan cleanup completes without error in test environment
  2. No unintended deletions occur (entries contain transaction data)
  3. Cascades work correctly after cleanup on all three tables: entries, holdings, account_providers
app/views/settings/providers/show.html.erb (1)

11-21: LGTM! Clean separation of SimpleFin UI.

The guard clause properly excludes SimpleFin from the generic provider loop, and the dedicated turbo-frame section enables dynamic UI updates specific to SimpleFin's enhanced workflows.

app/models/account.rb (2)

31-33: LGTM! Clean scope composition.

The new visible_manual scope provides a convenient way to query visible manual accounts by composing existing scopes.


170-178: LGTM! Formatting improvement.

The reformatted current_holdings method improves readability without changing behavior.

test/controllers/settings/providers_controller_test.rb (1)

11-18: LGTM! Clean refactor to configuration-based approach.

The test now uses Rails configuration stubs instead of environment variable overrides, aligning with the app-wide shift to configuration-based app mode detection.

lib/simplefin/date_utils.rb (1)

1-28: LGTM! Robust date parsing utility.

The implementation correctly handles multiple input types and uses UTC for epoch timestamps to ensure consistent date conversion across different timezone configurations. The error handling properly returns nil for unparseable inputs.

db/migrate/20251102143510_remove_duplicate_account_providers_index.rb (1)

1-18: LGTM! Safe and reversible index cleanup.

The migration properly removes the duplicate index with existence guards for idempotency and implements full reversibility. The inline documentation clearly explains the rationale.

app/models/account/provider_import_adapter.rb (2)

18-21: LGTM! Well-documented parameter additions.

The new notes and extra parameters are properly documented and extend the adapter to support richer transaction metadata from providers.


69-78: LGTM! Proper enrichment and metadata handling.

The implementation correctly:

  • Uses the enrichment pattern for notes to respect user overrides
  • Deep merges extra metadata with string keys for consistency
  • Includes appropriate guards and type checks
test/models/account/provider_import_adapter_test.rb (1)

313-313: LGTM! Improved test robustness with relative dates.

The date adjustments prevent conflicts with existing fixtures by using relative offsets instead of fixed dates. This makes the tests more reliable.

db/migrate/20251115194500_allow_null_merchant_id_on_recurring_transactions.rb (1)

1-5: LGTM!

The migration correctly allows NULL values on the merchant_id column. The use of change is appropriate since change_column_null is reversible.

app/views/settings/providers/_simplefin_panel.html.erb (1)

1-43: LGTM!

The SimpleFin panel structure is clean and follows the design system guidelines. The previous concern about using raw color utilities has been addressed (line 37 now uses bg-success). The auto-submit pattern with Stimulus is appropriate for this use case.

db/migrate/20251104000100_add_unique_index_on_simplefin_accounts.rb (1)

1-19: LGTM!

The migration correctly implements a partial unique index to enforce at-most-one non-null account_id per (simplefin_item_id, account_id) pair. The idempotency checks in both up and down methods ensure safe re-runs.

lib/tasks/simplefin.rake (1)

34-117: LGTM!

The encryption task is well-structured with proper safeguards:

  • Defaults to dry-run mode for safety (addressed from past review)
  • Validates encryption configuration before proceeding
  • Implements batch processing with configurable limits
  • Includes comprehensive error handling and reporting
lib/tasks/simplefin_debug.rake (2)

28-65: LGTM!

The transaction debugging logic correctly handles the raw payload with appropriate sorting and formatting. Previous concerns about timestamp handling have been addressed.


67-105: LGTM!

The entries debugging task correctly uses database-level filtering with SQL injection protection (line 75) and eager-loads the entryable association (line 87) to avoid N+1 queries. Previous concerns have been addressed.

app/controllers/concerns/simplefin_items/maps_helper.rb (1)

9-78: LGTM!

The batch query approach correctly eliminates N+1 queries by precomputing family-level and item-level data. The per-item loop uses the precomputed results efficiently, and the error handling at line 66 is appropriate.

app/views/accounts/_account.html.erb (1)

36-36: LGTM—Investment accounts can now link providers.

This change logically extends provider linking to Investment accounts alongside Depository and CreditCard types, aligning with the SimpleFin enhancements that support investment account syncing.

test/models/recurring_transaction_test.rb (1)

243-246: LGTM—Skip guards handle schema migration transition.

The conditional skip guards appropriately handle environments where the merchant_id column is still NOT NULL. This allows the tests to coexist with the migration that makes merchant_id nullable for name-based recurring transactions.

Also applies to: 287-290

app/models/simplefin_account/processor.rb (2)

12-16: LGTM—Manual relink guard prevents auto-linking.

The early return when current_account is missing ensures relinking is manual and user-confirmed via the modal, addressing past concerns about error-prone auto-linking.


19-24: LGTM—Provider link maintenance with graceful error handling.

The call to ensure_account_provider! correctly maintains the AccountProvider linkage after processing, and the rescue block appropriately logs failures without interrupting the broader processing flow.

app/views/transactions/show.html.erb (1)

100-146: LGTM—Clean rendering with logic delegated to helper.

The "Additional details" section appropriately delegates business logic to the build_transaction_extra_details helper and focuses solely on rendering the structured data. The conditional rendering for SimpleFin vs. raw data is clear and maintainable.

app/models/simplefin_item.rb (2)

14-21: LGTM—Encryption readiness check supports multiple configurations.

The encryption_ready? method correctly checks for Active Record Encryption configuration in both Rails credentials and environment variables, supporting different deployment scenarios.


52-53: LGTM—Backward-compatible sync parameter addition.

The optional sync parameter allows the importer to track sync context while maintaining backward compatibility with existing calls that don't provide it.

app/views/simplefin_items/new.html.erb (1)

1-24: LGTM—Streamlined connection flow with accessible form.

The conversion from modal to standalone page simplifies the UI flow. The form now correctly uses f.label for proper accessibility and label-to-input association, addressing previous concerns. The inline error display and direct form structure are clear improvements.

app/helpers/transactions_helper.rb (1)

22-77: LGTM—Well-structured helper with proper error handling.

The build_transaction_extra_details helper cleanly extracts presentation logic from the view and returns a well-structured hash. The use of rescue StandardError (line 67) appropriately catches only expected errors, addressing the previous concern about bare rescue. The method handles SimpleFin-specific data, provider extras, and raw fallback gracefully.

app/services/simplefin_item/unlinker.rb (1)

1-17: Clean deprecation wrapper for backward compatibility.

The deprecation notice is clear, and the delegation to item.unlink_all! is straightforward. This approach maintains backward compatibility while encouraging migration to the new model-based API.

test/models/simplefin_entry/processor_test.rb (1)

1-56: Well-structured test with comprehensive assertions.

The test setup is clear, and the assertions thoroughly validate the extra metadata persistence, including the nested structure. The test correctly validates that payee, memo, description, and extra are all captured in the "simplefin" sub-hash.

lib/tasks/simplefin_unlink.rake (1)

1-64: Solid task implementation with proper safeguards.

The task correctly:

  • Defaults to non-destructive dry_run mode
  • Validates UUID format before processing
  • Uses recursive redaction to protect sensitive data
  • Provides clear JSON output for automation
app/models/simplefin_account.rb (2)

12-12: Good uniqueness validation to prevent duplicate upstream accounts.

The validation correctly scopes uniqueness to simplefin_item_id with allow_nil: true, which prevents duplicate SimpleFin account IDs within the same item while allowing temporary nil values during creation.


20-35: Correctly implements provider linking with proper scope.

The method correctly addresses the previous review concern by looking up the AccountProvider by provider_type and provider_id only, then assigning the account. This ensures that when a SimpleFin account is relinked to a different Sure account, the existing provider row is updated rather than attempting to create a duplicate.

The rescue block appropriately logs errors and returns nil for graceful degradation.

app/views/simplefin_items/select_existing_account.html.erb (1)

1-40: Clean modal implementation with helpful empty state.

The conditional rendering provides clear guidance when no accounts are available for linking, and the form structure uses DS components appropriately. The balance calculation pattern is consistent with other integrations in the codebase.

test/controllers/simplefin_items_controller_test.rb (3)

227-233: Clear test for empty-state rendering.

The test correctly validates that the modal shows helpful guidance when no SimpleFIN accounts are available for linking, matching the view implementation.


234-246: Good coverage of unlinking behavior.

This test validates that provider links and legacy foreign keys are properly cleaned up during deletion, ensuring data consistency.


249-274: Validates setup flow correctly handles linked vs unlinked accounts.

The test ensures that complete_account_setup only creates accounts for truly unlinked SimpleFIN accounts and preserves existing links, which is the correct behavior.

lib/tasks/simplefin_holdings_backfill.rake (1)

1-141: Comprehensive backfill task with good safeguards.

The task provides multiple selection strategies, defaults to safe dry-run mode, includes rate limiting via sleep_ms, and produces structured JSON output for automation. The recursive processing handles errors gracefully per-account without aborting the entire run.

lib/tasks/simplefin_backfill.rake (1)

84-88: Nice: coerces Arrays into a Relation before find_each.

Prevents NoMethodError and retains batching.

db/schema.rb (1)

931-935: LGTM: transactions.extra (jsonb) with GIN index.

Fits queryability for metadata and aligns with PR goals.

app/models/simplefin_item/importer.rb (1)

120-124: No action required—sync_stats serialization is properly configured.

The sync_stats attribute in the Sync model is already declared with JSON serialization (serialize :sync_stats, coder: JSON at app/models/sync.rb:24), so Rails will correctly handle Hash serialization/deserialization. The code is safe as written.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
lib/tasks/holdings_tools.rake (1)

70-74: Fix parameter name inconsistency in error message.

The error message on line 72 says "Provide id" but the task parameter is :snapshot_id. This inconsistency remains from the previous review and could confuse users about which argument to supply.

Apply this diff:

       unless id
-        puts({ ok: false, error: "usage", message: "Provide id" }.to_json)
+        puts({ ok: false, error: "usage", message: "Provide snapshot_id" }.to_json)
         exit 1
       end
app/controllers/simplefin_items_controller.rb (1)

143-152: Synchronous SimpleFin API call blocks request thread.

Line 146 performs a synchronous perform_sync call during the HTTP request. If the SimpleFin API is slow or unavailable, this blocks the request thread and degrades UX—a reliability anti-pattern. According to past review comments, this was supposedly addressed in commit 7848530, but the synchronous call remains in the current code.

Use an async pattern similar to the sync action (lines 132-141):

   def balances
+    unless @simplefin_item.syncing?
       sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-      SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end
 
     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end
🧹 Nitpick comments (4)
app/controllers/simplefin_items_controller.rb (4)

317-319: Clarify intent of rescuing disable! error.

Line 318 swallows exceptions from previous_account.disable! without logging. If disabling the orphaned account fails (e.g., due to validation errors or callbacks), this silently continues. While this might be intentional to avoid blocking the relink operation, it could hide legitimate issues.

Consider logging the error to aid debugging:

       if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id
-        previous_account.disable! rescue nil
+        begin
+          previous_account.disable!
+        rescue => e
+          Rails.logger.warn("Failed to disable orphaned account #{previous_account.id}: #{e.message}")
+        end
       end

362-362: Remove unnecessary cache-busting query parameter.

Line 362 appends cache_bust: SecureRandom.hex(6) to the redirect URL for non-Turbo requests. This is unusual and likely unnecessary—Rails routing will ignore unrecognized params, and browser caching is typically handled via HTTP headers. If this is intentional to force a page refresh, document why; otherwise, remove it.

-      redirect_to accounts_path(cache_bust: SecureRandom.hex(6)), notice: "Account successfully linked to SimpleFIN", status: :see_other
+      redirect_to accounts_path, notice: "Account successfully linked to SimpleFIN", status: :see_other

405-405: Specify exception type in rescue clause.

Line 405 uses a bare rescue which catches all exceptions, including SystemExit and SignalException. This could hide serious errors during balance parsing.

-        sfa_balance = (sfa.current_balance || sfa.available_balance).to_d rescue 0.to_d
+        begin
+          sfa_balance = (sfa.current_balance || sfa.available_balance).to_d
+        rescue TypeError, ArgumentError
+          sfa_balance = 0.to_d
+        end

428-429: Extract magic number as named constant.

Lines 428-429 use BigDecimal("1.00") as a threshold for detecting balance conflicts during last4 matching. This magic number makes the heuristic less transparent.

+    BALANCE_CONFLICT_THRESHOLD = BigDecimal("1.00")
+
     def compute_relink_candidates
       # ...
             begin
               ab = (cand.balance || cand.cash_balance || 0).to_d
-              if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BigDecimal("1.00")
+              if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BALANCE_CONFLICT_THRESHOLD
                 cand = nil
               end

Similarly, extract BigDecimal("0.01") at line 447 as BALANCE_PROXIMITY_THRESHOLD.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 297670a and a7a081d.

📒 Files selected for processing (3)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/models/recurring_transaction/identifier.rb (0 hunks)
  • lib/tasks/holdings_tools.rake (1 hunks)
💤 Files with no reviewable changes (1)
  • app/models/recurring_transaction/identifier.rb
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (6)
app/models/account.rb (2)
  • include (1-227)
  • destroy_later (154-157)
app/models/simplefin_item.rb (3)
  • include (1-198)
  • destroy_later (47-50)
  • accounts (38-45)
app/controllers/plaid_items_controller.rb (3)
  • sync (40-49)
  • new (4-14)
  • link_existing_account (68-91)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/concerns/simplefin_items/maps_helper.rb (1)
  • build_simplefin_maps_for (9-78)
app/models/simplefin_account.rb (1)
  • current_account (16-18)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb

[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 346-346: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 358-358: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (1)
lib/tasks/holdings_tools.rake (1)

17-66: Well-structured task with proper defaults and validation.

The seed_prev_snapshot task implementation is solid:

  • Correctly defaults dry_run to true for safety (lines 32-33)
  • Strict validation of dry_run values prevents accidental writes (lines 34-41)
  • Clear comment explaining the factor semantics (line 51)
  • Proper exception handling with detailed error reporting (lines 63-66)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/controllers/simplefin_items_controller.rb (1)

18-78: Refactor to use async Sync framework; synchronous API call during HTTP request blocks user interaction.

Line 31 calls import_latest_simplefin_data synchronously during the HTTP request, which blocks the request thread until SimpleFin responds. When the user receives the Turbo response (lines 54–63), they see success, but no data has been fetched yet if the API is slow.

This is architecturally inconsistent with the codebase. The Sync model and SimplefinItem::Syncer are designed for background jobs (see SimplefinItem::BalancesOnlyJob), but the update action bypasses this framework entirely.

Recommendation:

  • Create a SimplefinItem::UpdateJob that validates the token asynchronously and transfers accounts
  • Return immediate Turbo response with optimistic feedback (e.g., "Validating token...")
  • Broadcast completion/error status to the UI once the job completes
  • This matches the pattern already established for balances-only syncs and avoids blocking the request thread

Alternatively, if token validation is non-negotiable before marking update complete, add a code comment explaining why synchronous execution is safe and necessary here.

♻️ Duplicate comments (1)
app/controllers/simplefin_items_controller.rb (1)

143-152: Move synchronous SimpleFin API call to background job.

Line 146 calls perform_sync synchronously in the request thread. If the SimpleFin API is slow or unavailable, this blocks the request thread and degrades user experience. The PR objectives explicitly mention "backgrounded balances import for faster UI", suggesting this should be async.

Apply this diff to make balances-only sync asynchronous:

   def balances
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+    unless @simplefin_item.syncing?
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end
 
     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

Alternatively, create a dedicated SimplefinItemBalancesSyncJob if balances-only syncs need different handling than full syncs.

🧹 Nitpick comments (1)
app/controllers/simplefin_items_controller.rb (1)

407-501: Consider defensive logging and refactoring for testability.

The compute_relink_candidates method implements sophisticated heuristic matching (last4 → balance proximity → name) with appropriate ambiguity and conflict guards. The logic appears sound overall.

However, a few improvements would help maintainability:

  1. Line 409: rescue nil silently swallows all dedup errors, which could hide issues. Consider logging exceptions:

    begin
      @simplefin_item.dedup_simplefin_accounts!
    rescue => e
      Rails.logger.warn("SimpleFin dedup failed before relink: #{e.class} - #{e.message}")
    end
  2. Line 453: The $1.00 threshold for balance conflict detection is somewhat arbitrary. Consider extracting as a constant:

    BALANCE_CONFLICT_THRESHOLD = BigDecimal("1.00")
  3. Complexity: The method has high cyclomatic complexity. Consider extracting the three matching strategies into separate private methods:

    def match_by_last4(sfa, manuals, used_ids, sfa_balance)
      # lines 435-463
    end
    
    def match_by_balance(sfa, manuals, used_ids, sfa_balance)
      # lines 467-480
    end
    
    def match_by_name(sfa, manuals, used_ids)
      # lines 483-489
    end

    This would improve testability and readability without changing behavior.

The used_manual_ids tracking and 1:1 matching constraints are well-designed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7a081d and 344f3d6.

📒 Files selected for processing (2)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
  • app/views/simplefin_items/errors.html.erb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/views/simplefin_items/errors.html.erb
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (4)
app/models/account.rb (2)
  • include (1-227)
  • destroy_later (154-157)
app/models/simplefin_item.rb (3)
  • include (1-198)
  • destroy_later (47-50)
  • accounts (38-45)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/concerns/simplefin_items/maps_helper.rb (1)
  • build_simplefin_maps_for (9-78)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb

[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 346-346: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 358-358: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (4)
app/controllers/simplefin_items_controller.rb (4)

54-63: LGTM: Turbo Frame handling is well-implemented.

The Turbo Stream responses throughout the controller (update, create, complete_account_setup, link_existing_account) properly:

  • Check turbo_frame_request? before rendering streams
  • Replace/update appropriate DOM targets
  • Include flash notification streams
  • Rebuild data models (manual accounts, SimpleFin maps) to ensure UI consistency
  • Use proper fallback redirects for non-Turbo requests

The pattern is consistent and follows Turbo conventions correctly.

Also applies to: 95-104, 222-254, 322-363


274-320: LGTM: Solid guard and relink logic.

The guard at lines 279-286 correctly ensures only manual accounts (no existing provider links or legacy IDs) can be linked, preventing data corruption from double-linking.

The relink behavior (lines 300-320) is well-designed:

  • Uses transaction with explicit lock to prevent race conditions
  • Clears legacy association before upsetting provider link
  • Tracks previous account and disables it if orphaned (clever cleanup)
  • Swallows errors from orphan disable (acceptable since it's cleanup)

The deterministic upsert pattern via find_or_initialize_by(provider: simplefin_account) ensures no duplicate AccountProvider records.


366-394: LGTM: Clean error aggregation for modal display.

The errors action properly:

  • Finds the latest sync (with optimization for loaded associations)
  • Extracts and normalizes errors from sync_stats (handling both Hash and String formats)
  • Falls back to item-level sync_error if present
  • Deduplicates and filters empty messages
  • Renders as a modal (layout: false)

This is a straightforward, defensive implementation for surfacing sync errors to users.


257-272: LGTM: Clean modal preparation with intentional design.

The select_existing_account action properly:

  • Filters out the no-op case (SFA already linked to this account)
  • Intentionally keeps SFAs linked to other accounts (to allow reassignment in unified flow, per comment)
  • Orders newest-first for better UX
  • Always renders a modal (either with choices or empty-state)

The filtering logic at line 266 correctly uses account_provider&.account_id to safely check the current link.

Copy link
Collaborator

@sokie sokie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it also and works good and is stable.
with the account creation and unlinking it also looks really good!

Copy link
Collaborator

@sokie sokie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connection and stability is really good, tested it and it works great.
Unfortunately I found a bug with the linking feature.
It seems I can link accounts from "Other" to SimpleFIN accounts that are already linked and synced.
This means I end up getting multiple acocunts linked to the same remote SimpleFIn account ( screenshot 2 ).
In the Lunch Flow link feature, I used to check for accounts that are already linked, and not show those, that functionality seems to also be broken now. Also when I create accounts.

Image Image

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/controllers/simplefin_items_controller.rb (1)

143-152: Critical: Synchronous API call blocks request thread.

Line 146 calls perform_sync synchronously during the HTTP request. Even though the Syncer has a "fast path" for balances-only syncs, this blocks the request thread if the SimpleFin API is slow, rate-limited, or unavailable, degrading UX and tying up application resources.

The sync action (lines 132-141) demonstrates the correct async pattern using sync_later. Apply a similar approach here:

   def balances
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+    unless @simplefin_item.syncing?
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end
 
     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

Alternatively, if you intentionally keep it synchronous because balances-only is guaranteed lightweight, add a clear comment explaining why inline execution is safe.

🧹 Nitpick comments (2)
app/controllers/simplefin_items_controller.rb (2)

321-339: Remove unnecessary reload.

Line 339 reloads @account, but the transaction (lines 299-319) doesn't modify the account record itself—only its associations. The reload is unnecessary and adds a redundant database query.

       flash[:notice] = "Account successfully linked to SimpleFIN"
-      @account.reload
       manual_accounts_stream = if @manual_accounts.any?

448-470: Extract magic numbers to named constants.

Lines 452 and 470 use hardcoded thresholds (1.00 for balance conflict detection and 0.01 for balance proximity matching) that would be clearer as named constants.

+  # Relink heuristic thresholds
+  BALANCE_CONFLICT_THRESHOLD = BigDecimal("1.00")
+  BALANCE_PROXIMITY_THRESHOLD = BigDecimal("0.01")
+
   def compute_relink_candidates
     # ...
     
     # Conflict guard: if both have balances and differ wildly, skip
     begin
       ab = (cand.balance || cand.cash_balance || 0).to_d
-      if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BigDecimal("1.00")
+      if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BALANCE_CONFLICT_THRESHOLD
         cand = nil
       end
     rescue
       # ...
     end
     
     # ...
     
     balance_matches = manuals.reject { |a| used_manual_ids.include?(a.id) }.select do |a|
       begin
         ab = (a.balance || a.cash_balance || 0).to_d
-        (ab - sfa_balance).abs <= BigDecimal("0.01")
+        (ab - sfa_balance).abs <= BALANCE_PROXIMITY_THRESHOLD
       rescue
         false
       end
     end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 344f3d6 and eb089cd.

📒 Files selected for processing (1)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (4)
app/models/account.rb (2)
  • include (1-227)
  • destroy_later (154-157)
app/models/simplefin_item.rb (3)
  • include (1-198)
  • destroy_later (47-50)
  • accounts (38-45)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/concerns/simplefin_items/maps_helper.rb (1)
  • build_simplefin_maps_for (9-78)
🪛 ast-grep (0.39.9)
app/controllers/simplefin_items_controller.rb

[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 345-345: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 357-357: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci / test
🔇 Additional comments (4)
app/controllers/simplefin_items_controller.rb (4)

365-393: LGTM!

The errors action demonstrates good defensive coding: it handles both loaded and unloaded associations (lines 367-371), normalizes error formats from Hash or String (lines 376-382), falls back to item-level errors (lines 385-387), and deduplicates the results (line 390).


54-63: LGTM: Consistent Turbo Stream patterns.

The Turbo Stream handling across update, create, complete_account_setup, and link_existing_account follows a consistent pattern: conditional rendering based on turbo_frame_request?, reloading data and recomputing UI maps via build_simplefin_maps_for (which uses batch queries per the MapsHelper), targeted DOM updates via multiple stream operations, and graceful fallback to redirects for non-Turbo requests. The pattern aligns with Rails and Turbo best practices.

Also applies to: 95-104, 222-254, 321-362


121-129: LGTM: Proper cleanup before deletion.

The destroy action now calls unlink_all! (lines 122-127) to detach provider links and legacy associations before scheduling deletion, preventing foreign key violations and orphaned records. The rescue block ensures the deletion proceeds even if unlinking fails, which is appropriate for a destroy operation.


2-3: LGTM: Clean module inclusion and before_action.

The SimplefinItems::MapsHelper inclusion (line 2) provides the build_simplefin_maps_for method used throughout the controller, and the before_action list (line 3) correctly covers all actions that require @simplefin_item to be set.

Copy link
Collaborator

@sokie sokie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and gtg

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

# Starts a balances-only sync for this SimpleFin item
def balances
sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Blocking Sync Harms User Experience

The balances action performs a synchronous sync operation (SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)) directly in the controller request cycle, which can cause timeouts and poor user experience for long-running operations. The sync should be enqueued as a background job instead, similar to how the sync action uses sync_later. This blocks the HTTP response until the entire sync completes, potentially causing request timeouts.

Fix in Cursor Fix in Web

# follows the chosen account.
if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id
previous_account.disable! rescue nil
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect account disabling overlooks active links.

When relinking a SimpleFin account to a different manual account, the code disables previous_account without verifying it's actually orphaned. If previous_account has other active account_providers links (from different providers like Plaid), it will still be disabled incorrectly. The check should reload previous_account after the provider link update and verify previous_account.account_providers.none? before disabling.

Fix in Cursor Fix in Web


# Return without the reason field to the view
pairs.map { |p| p.slice(:sfa_id, :sfa_name, :manual_id, :manual_name) }
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Clean Up Obsolete Auto-Linking Code

The compute_relink_candidates method (and its helper normalize_name) is defined but never called anywhere in the controller or codebase. This appears to be dead code from a removed auto-linking feature mentioned in the PR discussion, where the team decided to make linking manual-only instead of automatic.

Fix in Cursor Fix in Web

format.html { redirect_back_or_to accounts_path }
format.json { render json: { ok: true, sync_id: sync.id } }
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Synchronous Balances Sync Degrades User Experience

The balances action performs a sync synchronously by calling SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) directly in the controller, blocking the HTTP request until completion. This differs from the sync action which uses sync_later for async processing. A SimplefinItem::BalancesOnlyJob exists but is never enqueued. Synchronous syncs can timeout or cause poor UX, especially with network delays or rate limits.

Fix in Cursor Fix in Web

end

simplefin_account.save!
begin
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Deduplication Flaw Hides Unique Transactions

Transaction deduplication in import_account uses tx[:id] || tx[:fitid] || [tx[:posted], tx[:amount], tx[:description]] as the uniqueness key. When both tx[:id] and tx[:fitid] are nil, it falls back to an array composite key. This incorrectly treats legitimately different transactions as duplicates if they share the same posted date, amount, and description (e.g., two identical purchases on the same day from the same merchant).

Fix in Cursor Fix in Web

Copy link
Collaborator

@jjmata jjmata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wohoo! 🦣 PR going in ...

t.decimal "expected_amount_avg", precision: 19, scale: 4
t.index ["family_id", "merchant_id", "amount", "currency"], name: "idx_recurring_txns_merchant", unique: true, where: "(merchant_id IS NOT NULL)"
t.index ["family_id", "name", "amount", "currency"], name: "idx_recurring_txns_name", unique: true, where: "((name IS NOT NULL) AND (merchant_id IS NULL))"
t.index ["family_id", "merchant_id", "amount", "currency"], name: "idx_recurring_txns_on_family_merchant_amount_currency", unique: true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Nulls Compromise Recurring Transaction Uniqueness

The unique index idx_recurring_txns_on_family_merchant_amount_currency on recurring_transactions includes merchant_id but doesn't handle NULL values correctly. After migration 20251115194500 allows merchant_id to be NULL, the unique constraint will fail for name-based recurring transactions (where merchant_id is NULL) because multiple NULL values in a unique index are treated as distinct in PostgreSQL. This breaks the uniqueness guarantee for name-based patterns with the same family/amount/currency, allowing duplicate recurring transaction records.

Fix in Cursor Fix in Web

# Starts a balances-only sync for this SimpleFin item
def balances
sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Balances Sync: Unblock User Experience

The balances action performs a synchronous sync by calling SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync) directly in the controller, blocking the HTTP request until completion. This can cause request timeouts for slow API calls and prevents the user from seeing progress updates. The sync should be enqueued as a background job like the sync action does with sync_later.

Fix in Cursor Fix in Web

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
app/controllers/simplefin_items_controller.rb (2)

298-319: Major: Disabling account overlooks other active provider links.

Lines 316-317 disable previous_account after relinking, but fail to check whether it has other active AccountProvider records (e.g., from Plaid or other SimpleFin items). This incorrectly hides accounts that still have live provider data.

Scenario:

  1. Manual account M has SimpleFin provider link (AP1)
  2. M also has Plaid provider link (AP2)
  3. User relinks SimpleFin account to M2
  4. Code updates AP1 to point to M2
  5. Code disables M ← WRONG: M still has AP2!

Apply this diff:

       if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id
-        previous_account.disable! rescue nil
+        previous_account.reload
+        if previous_account.account_providers.none?
+          previous_account.disable! rescue nil
+        end
       end

143-152: Critical: Synchronous API call blocks HTTP request.

The balances action calls perform_sync directly at Line 146, blocking the request thread until the SimpleFin API responds. This is a reliability anti-pattern that can cause timeouts and poor UX, especially under rate limits or network delays.

The sync action (lines 132-141) demonstrates the correct async pattern using sync_later. Apply the same approach here.

Apply this diff:

   def balances
-    sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
-    SimplefinItem::Syncer.new(@simplefin_item).perform_sync(sync)
+    unless @simplefin_item.syncing?
+      sync = @simplefin_item.syncs.create!(status: :pending, sync_stats: { "balances_only" => true })
+      SimplefinItemSyncJob.perform_later(@simplefin_item.id, sync.id)
+    end
 
     respond_to do |format|
       format.html { redirect_back_or_to accounts_path }
-      format.json { render json: { ok: true, sync_id: sync.id } }
+      format.json { render json: { ok: true } }
     end
   end

Alternatively, if a dedicated job exists for balances-only syncs, use that instead.

🧹 Nitpick comments (1)
app/controllers/simplefin_items_controller.rb (1)

397-500: Optional: Remove unused auto-linking code.

The compute_relink_candidates method (and its helper normalize_name) is never called in this controller or elsewhere in the codebase. According to PR discussion, the team chose manual-only linking via checkboxes instead of automatic heuristics.

The actual linking flow uses select_existing_account + link_existing_account actions (lines 257-363), which don't invoke this code.

Consider removing 100+ lines of dead code to reduce maintenance burden:

   private
 
-    NAME_NORM_RE = /\s+/.freeze
-
-
-    def normalize_name(str)
-      s = str.to_s.downcase.strip
-      return s if s.empty?
-      s.gsub(NAME_NORM_RE, " ")
-    end
-
-    def compute_relink_candidates
-      # ... entire method (lines 406-500) ...
-    end
-
     def set_simplefin_item
       @simplefin_item = Current.family.simplefin_items.find(params[:id])
     end

If you plan to use these heuristics in future UI features, consider moving them to a service object or model concern instead of a controller private method.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27a0d90 and 1b8b364.

📒 Files selected for processing (2)
  • app/controllers/settings/bank_sync_controller.rb (1 hunks)
  • app/controllers/simplefin_items_controller.rb (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/controllers/settings/bank_sync_controller.rb
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.
Learnt from: Himmelschmidt
Repo: we-promise/sure PR: 104
File: app/models/simplefin_item/importer.rb:34-45
Timestamp: 2025-09-03T01:15:59.638Z
Learning: In SimpleFin integration, the default initial sync period is intentionally set to 7 days (not 3 years) to provide quick setup, with users able to optionally extend to 3 years via sync_start_date selection. This design balances setup speed with comprehensive data access options.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs used with "simplefin_" prefix as plaid_id values have very low collision risk and don't require scoped uniqueness constraints, as SimpleFin provides globally unique transaction identifiers across their system.
Learnt from: jjmata
Repo: we-promise/sure PR: 104
File: app/models/simplefin_account/transactions/processor.rb:36-41
Timestamp: 2025-08-22T23:28:38.143Z
Learning: SimpleFin transaction IDs are globally unique within SimpleFin's system, and the "simplefin_" prefix provides sufficient namespace separation from other providers like Plaid, making composite uniqueness constraints on entries.plaid_id unnecessary for SimpleFin integrations.
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_entry/processor.rb:10-12
Timestamp: 2025-09-13T11:40:26.913Z
Learning: The plaid_id field is used consistently across multiple banking integrations (Plaid, SimpleFin, and Enable Banking) as a common external identifier field. A comprehensive refactor to use a proper external_id field should be done across all integrations in a separate PR to maintain consistency and avoid scope creep.
📚 Learning: 2025-09-23T22:23:22.963Z
Learnt from: CR
Repo: we-promise/sure PR: 0
File: .cursor/rules/view_conventions.mdc:0-0
Timestamp: 2025-09-23T22:23:22.963Z
Learning: Applies to app/{views,components}/**/*.html.erb : Integrate Stimulus declaratively in ERB: templates declare data-controller/actions/targets; controllers respond to those declarations

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:27:55.271Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/controllers/enable_banking_items_controller.rb:48-62
Timestamp: 2025-09-13T11:27:55.271Z
Learning: The `create_enable_banking_item!` method in `Family::EnableBankingConnectable` uses `find_or_create_by(id: enable_banking_id)` to either find an existing EnableBankingItem or create a new one, so it automatically handles updates to existing items without creating duplicates.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T12:23:31.324Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: db/migrate/20250825224717_add_enable_banking_domain.rb:3-34
Timestamp: 2025-09-13T12:23:31.324Z
Learning: When suggesting database schema improvements for banking integrations, consider consistency across all similar integrations (Plaid, SimpleFin, Enable Banking) and propose comprehensive changes in a separate PR rather than making changes to individual integrations in isolation.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method pattern is consistent across banking integrations in the codebase. Both PlaidAccount::Processor and EnableBankingAccount::Processor use the same approach: `balance = account.current_balance || account.available_balance || 0` followed by `OpenStruct.new(balance: balance, cash_balance: balance)` without explicit requires, and include identical comments about not distinguishing cash vs non-cash balances for non-investment accounts.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:33:54.159Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account.rb:9-20
Timestamp: 2025-09-13T11:33:54.159Z
Learning: The EnableBankingAccount model uses a `has_balance` validation method that ensures at least one balance value (current_balance or available_balance) is present, similar to the Plaid integration pattern. This means balance values should not be nil and the validation expects actual balance data from the API.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
📚 Learning: 2025-09-13T11:36:45.479Z
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/models/enable_banking_account/processor.rb:72-79
Timestamp: 2025-09-13T11:36:45.479Z
Learning: The balance_calculator method in EnableBankingAccount::Processor follows the same pattern as the Plaid integration, using OpenStruct without explicit requires and returning balance values as-is without BigDecimal coercion for consistency across banking integrations.

Applied to files:

  • app/controllers/simplefin_items_controller.rb
🧬 Code graph analysis (1)
app/controllers/simplefin_items_controller.rb (4)
app/models/account.rb (2)
  • include (1-227)
  • destroy_later (154-157)
app/models/simplefin_item.rb (3)
  • include (1-198)
  • destroy_later (47-50)
  • accounts (38-45)
app/models/simplefin_item/syncer.rb (1)
  • perform_sync (8-48)
app/controllers/concerns/simplefin_items/maps_helper.rb (1)
  • build_simplefin_maps_for (9-78)
🪛 ast-grep (0.40.0)
app/controllers/simplefin_items_controller.rb

[warning] 240-240: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 345-345: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)


[warning] 357-357: Found the use of an hardcoded passphrase for RSA. The passphrase can be easily discovered, and therefore should not be stored in source-code. It is recommended to remove the passphrase from source-code, and use system environment variables or a restricted configuration file.
Context: turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
Note: [CWE-798]: Use of Hard-coded Credentials [OWASP A07:2021]: Identification and Authentication Failures [REFERENCES]
https://cwe.mitre.org/data/definitions/522.html

(hardcoded-secret-rsa-passphrase-ruby)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: ci / test

@jjmata jjmata merged commit 61eb611 into we-promise:main Nov 17, 2025
7 checks passed
@luckyPipewrench luckyPipewrench deleted the simplefin-enhancements-v2 branch November 17, 2025 20:54
jjmata pushed a commit that referenced this pull request Nov 17, 2025
This commit addresses the remaining unresolved code review comments:

1. Fix down migration in drop_was_merged_from_transactions.rb
   - Add null: false, default: false constraints to match original column
   - Ensures proper rollback compatibility

2. Fix bare rescue in maps_helper.rb compute_duplicate_only_flag
   - Replace bare rescue with rescue StandardError => e
   - Add proper logging for debugging
   - Follows Ruby best practices by being explicit about exception handling

These changes improve code quality and follow Rails/Ruby best practices.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: SimpleFIN import fails when account currency is blank bug: Initial SimpleFIN account sync failures

4 participants