diff --git a/app/components/goals/funding_accounts_breakdown_component.rb b/app/components/goals/funding_accounts_breakdown_component.rb index d977ab578..294ff0825 100644 --- a/app/components/goals/funding_accounts_breakdown_component.rb +++ b/app/components/goals/funding_accounts_breakdown_component.rb @@ -105,7 +105,8 @@ class Goals::FundingAccountsBreakdownComponent < ApplicationComponent result end rescue StandardError => e - Rails.logger.warn("Sparkline map for goal #{goal.id} failed: #{e.message}") + Rails.logger.error("Sparkline map for goal #{goal.id} failed: #{e.class}: #{e.message}") + Sentry.capture_exception(e) if defined?(Sentry) {} end end diff --git a/app/javascript/controllers/goal_stepper_controller.js b/app/javascript/controllers/goal_stepper_controller.js index d92b20faa..dc07e7734 100644 --- a/app/javascript/controllers/goal_stepper_controller.js +++ b/app/javascript/controllers/goal_stepper_controller.js @@ -3,8 +3,8 @@ import { Controller } from "@hotwired/stimulus"; // 2-step modal stepper for creating a goal. // // Single
with two panels. Step 1 collects identity (name, amount, -// date, color, notes, linked accounts). Step 2 reviews + optional initial -// contribution. All state lives in the DOM — no half-records, single POST. +// date, color, notes, linked accounts). Step 2 reviews and submits. All +// state lives in the DOM — no half-records, single POST. export default class extends Controller { static targets = [ "step1Panel", @@ -22,8 +22,6 @@ export default class extends Controller { "amountError", "accountsError", "linkedAccountCheckbox", - "initialContributionAmount", - "initialContributionAccountSelect", "reviewName", "reviewSummary", "reviewSuggested", @@ -85,7 +83,6 @@ export default class extends Controller { this.step1PanelTarget.classList.add("hidden"); this.step2PanelTarget.classList.remove("hidden"); this.updateStepperState(); - this.refreshAccountSelect(); this.updateReview(); this.updateFooter(); } @@ -99,7 +96,6 @@ export default class extends Controller { } linkedAccountChanged() { - this.refreshAccountSelect(); this.refreshSubmitState(); this.updateReview(); if (this.linkedAccountCheckboxTargets.some((cb) => cb.checked) && this.hasAccountsErrorTarget) { @@ -169,26 +165,6 @@ export default class extends Controller { this.footerRightButtonTarget.classList.toggle("opacity-50", !anyChecked && this.currentStep === 1); } - refreshAccountSelect() { - if (!this.hasInitialContributionAccountSelectTarget) return; - const select = this.initialContributionAccountSelectTarget; - const previous = select.value; - while (select.options.length > 1) select.remove(1); - - this.linkedAccountCheckboxTargets - .filter((cb) => cb.checked) - .forEach((cb) => { - const opt = document.createElement("option"); - opt.value = cb.value; - opt.textContent = cb.dataset.accountName || cb.value; - select.appendChild(opt); - }); - - if ([...select.options].some((o) => o.value === previous)) { - select.value = previous; - } - } - updateStepperState() { if (this.hasStep1CircleTarget) { this.step1CircleTarget.classList.toggle("bg-inverse", this.currentStep === 1); diff --git a/app/models/demo/generator.rb b/app/models/demo/generator.rb index d5d1c0ec8..b60b293ae 100644 --- a/app/models/demo/generator.rb +++ b/app/models/demo/generator.rb @@ -1285,102 +1285,57 @@ class Demo::Generator currency = depository_accounts.first.currency eligible = depository_accounts.select { |a| a.currency == currency } primary = eligible.first - secondary = eligible[1] || primary - - # Build "Wedding fund" on_track contributions: target 12 months out, - # $200/mo required, demo contributes $220/mo for last 6 months → on - # pace. - wedding_contribs = (0..5).map do |i| - { amount: 220, source: i.zero? ? "initial" : "manual", days_ago: 30 * (6 - i), account: primary } - end - - # "House downpayment" gets a fuller contribution history so the - # scrollable list has real density. - house_contribs = [ - { amount: 5_000, source: "initial", days_ago: 365, account: primary }, - { amount: 750, source: "manual", days_ago: 330, account: primary }, - { amount: 750, source: "manual", days_ago: 300, account: secondary }, - { amount: 750, source: "manual", days_ago: 270, account: primary }, - { amount: 750, source: "manual", days_ago: 240, account: primary }, - { amount: 750, source: "manual", days_ago: 210, account: secondary }, - { amount: 750, source: "manual", days_ago: 180, account: primary }, - { amount: 750, source: "manual", days_ago: 150, account: primary }, - { amount: 750, source: "manual", days_ago: 120, account: secondary }, - { amount: 750, source: "manual", days_ago: 90, account: primary }, - { amount: 750, source: "manual", days_ago: 60, account: primary }, - { amount: 750, source: "manual", days_ago: 30, account: secondary } - ] + # V2 goals derive balance + pace from the linked depository accounts + # directly; the demo's contribution arrays were V1 ledger seed data + # and have nothing to consume them now. Account-level transaction + # seeding (paychecks, etc.) elsewhere in this generator already + # populates the goal pace/balance. goals = [ { name: "Vacation in Italy", target: 5_000, target_date: 4.months.from_now.to_date, - accounts: eligible.first(2), - contributions: [ - { amount: 500, source: "initial", days_ago: 90, account: primary }, - { amount: 250, source: "manual", days_ago: 60, account: primary }, - { amount: 250, source: "manual", days_ago: 30, account: secondary } - ] + accounts: eligible.first(2) }, { name: "Wedding fund", target: 2_400, target_date: 6.months.from_now.to_date, - accounts: eligible.first(2), - contributions: wedding_contribs + accounts: eligible.first(2) }, { name: "Emergency fund", target: 10_000, target_date: nil, - accounts: [ primary ], - contributions: [ - { amount: 1_000, source: "initial", days_ago: 180, account: primary }, - { amount: 250, source: "manual", days_ago: 60, account: primary }, - { amount: 250, source: "manual", days_ago: 30, account: primary } - ] + accounts: [ primary ] }, { name: "House downpayment", target: 50_000, target_date: 24.months.from_now.to_date, - accounts: eligible.first(2), - contributions: house_contribs + accounts: eligible.first(2) }, { name: "Sabbatical", target: 15_000, target_date: 18.months.from_now.to_date, state: "paused", - accounts: [ primary ], - contributions: [ - { amount: 1_500, source: "initial", days_ago: 200, account: primary }, - { amount: 500, source: "manual", days_ago: 150, account: primary } - ] + accounts: [ primary ] }, { name: "Old laptop fund", target: 1_500, target_date: 12.months.ago.to_date, state: "archived", - accounts: [ primary ], - contributions: [ - { amount: 400, source: "initial", days_ago: 540, account: primary } - ] + accounts: [ primary ] }, { name: "Paid-off car", target: 8_000, target_date: 6.months.ago.to_date, state: "completed", - accounts: [ primary ], - contributions: [ - { amount: 2_000, source: "initial", days_ago: 730, account: primary }, - { amount: 2_000, source: "manual", days_ago: 600, account: primary }, - { amount: 2_000, source: "manual", days_ago: 450, account: primary }, - { amount: 2_000, source: "manual", days_ago: 300, account: primary } - ] + accounts: [ primary ] } ] diff --git a/app/models/goal.rb b/app/models/goal.rb index b57abcf2c..db50da609 100644 --- a/app/models/goal.rb +++ b/app/models/goal.rb @@ -297,7 +297,10 @@ class Goal < ApplicationRecord period: Period.last_90_days ).balance_series.values rescue StandardError => e - Rails.logger.warn("Goal##{id} balance series failed: #{e.message}") + # Degrade gracefully (chart drops to target-line-only) but surface + # the failure — silent fallbacks here masked real Builder bugs. + Rails.logger.error("Goal##{id} balance series failed: #{e.class}: #{e.message}") + Sentry.capture_exception(e) if defined?(Sentry) [] end