From 91baa62604fb299e441cd094e31cde7a742ac57a Mon Sep 17 00:00:00 2001 From: Guillem Arias Date: Wed, 27 May 2026 10:14:13 +0200 Subject: [PATCH] fix(goals): cover money displays with privacy-sensitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit-driven sweep. The class was already on the obvious surfaces (KPI strip, ring center, card balance, funding-accounts breakdown); these were the secondary surfaces missed in the initial PR — money interpolated into descriptive prose, account-picker balances, live previews, and the projection chart tooltip. - card_component: target divisor next to the masked balance, pace line, and behind-status footer (`footer_has_money?` helper keeps non-money branches unmasked so paused / archived / "Goal reached" copy stays readable in privacy mode). - show: header_summary (target + date subtitle), to_go remaining, inactive recap body, celebration body, catch_up body. - _status_callout: conditional on `goal.status == :behind` — only that branch carries an amount; on_track / no_target_date have date or static copy. - _form_edit + _form_stepper: account balance shown in the linked- account picker rows. - _form_stepper review section: reviewSummary + reviewSuggested ps (Stimulus injects target / suggested $X/mo into both). - _pending_pledge_banner: banner title span (amount + account + days). - goal_pledges/new: live preview p (Stimulus injects "Reaches X%, $A of $B" / "Hits your $B target"). - goal_projection_chart_controller: tooltip was inline-styled with hard-coded gray-900 + white (DS drift) and had no privacy class. Replaced cssText with className using bg-container + text-primary + border-secondary + rounded-lg + privacy-sensitive — mirrors the pattern in time_series_chart_controller and the post-#1996 sankey fix. Tooltip now respects theme and privacy mode. --- app/components/goals/card_component.html.erb | 6 +++--- app/components/goals/card_component.rb | 4 ++++ .../controllers/goal_projection_chart_controller.js | 3 ++- app/views/goal_pledges/new.html.erb | 2 +- app/views/goals/_form_edit.html.erb | 2 +- app/views/goals/_form_stepper.html.erb | 6 +++--- app/views/goals/_pending_pledge_banner.html.erb | 2 +- app/views/goals/_status_callout.html.erb | 2 +- app/views/goals/show.html.erb | 10 +++++----- 9 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/components/goals/card_component.html.erb b/app/components/goals/card_component.html.erb index 51684e8fd..7e2174885 100644 --- a/app/components/goals/card_component.html.erb +++ b/app/components/goals/card_component.html.erb @@ -46,10 +46,10 @@
<%= goal.current_balance_money.format(precision: 0) %> - / <%= goal.target_amount_money.format(precision: 0) %> + / <%= goal.target_amount_money.format(precision: 0) %>
<% if pace_line %> -

<%= pace_line %>

+

<%= pace_line %>

<% end %>
@@ -58,7 +58,7 @@ <%= render Goals::AccountStackComponent.new(accounts: linked_accounts, color_map: goal.account_color_map) %> <%= linked_accounts_count_label %> - + "> <%= footer_line %><% if has_pending_pledge? %> · <%= t("goals.goal_card.pending_count", count: pending_pledges_count) %><% end %> diff --git a/app/components/goals/card_component.rb b/app/components/goals/card_component.rb index 34fe7fd6f..0f68d4839 100644 --- a/app/components/goals/card_component.rb +++ b/app/components/goals/card_component.rb @@ -113,4 +113,8 @@ class Goals::CardComponent < ApplicationComponent end end end + + def footer_has_money? + goal.status == :behind && goal.monthly_target_amount + end end diff --git a/app/javascript/controllers/goal_projection_chart_controller.js b/app/javascript/controllers/goal_projection_chart_controller.js index 2b60c08c5..cefddedeb 100644 --- a/app/javascript/controllers/goal_projection_chart_controller.js +++ b/app/javascript/controllers/goal_projection_chart_controller.js @@ -440,7 +440,8 @@ export default class extends Controller { // own `relative`. Read the computed style instead. if (getComputedStyle(root).position === "static") root.style.position = "relative"; const tooltip = document.createElement("div"); - tooltip.style.cssText = "position:absolute;pointer-events:none;display:none;background:var(--color-gray-900);color:var(--color-white);font-size:12px;line-height:1.35;padding:6px 8px;border-radius:6px;white-space:nowrap;z-index:5;box-shadow:0 2px 8px rgba(0,0,0,0.15);"; + tooltip.className = "bg-container text-primary text-sm font-sans absolute p-2 border border-secondary rounded-lg pointer-events-none z-50 privacy-sensitive"; + tooltip.style.display = "none"; root.appendChild(tooltip); const overlay = svg diff --git a/app/views/goal_pledges/new.html.erb b/app/views/goal_pledges/new.html.erb index a8c310efc..ac5767c95 100644 --- a/app/views/goal_pledges/new.html.erb +++ b/app/views/goal_pledges/new.html.erb @@ -44,7 +44,7 @@ action: "input->goal-pledge-preview#update" } %> -

<%= f.select :account_id, diff --git a/app/views/goals/_form_edit.html.erb b/app/views/goals/_form_edit.html.erb index ba088d055..784c8e645 100644 --- a/app/views/goals/_form_edit.html.erb +++ b/app/views/goals/_form_edit.html.erb @@ -46,7 +46,7 @@

<%= account.name %>

<%= (account.subtype || subtype).titleize %>

- <%= Money.new(account.balance, account.currency).format %> + <%= Money.new(account.balance, account.currency).format %> <% end %> diff --git a/app/views/goals/_form_stepper.html.erb b/app/views/goals/_form_stepper.html.erb index 06471bfcc..a9cb8481d 100644 --- a/app/views/goals/_form_stepper.html.erb +++ b/app/views/goals/_form_stepper.html.erb @@ -86,7 +86,7 @@

<%= account.name %>

<%= (account.subtype || subtype).titleize %>

- <%= Money.new(account.balance, account.currency).format %> + <%= Money.new(account.balance, account.currency).format %> <% end %> @@ -115,11 +115,11 @@ <%= render DS::FilledIcon.new(variant: :container, icon: "target", size: "lg", rounded: false) %>

-

+

-

+

diff --git a/app/views/goals/_pending_pledge_banner.html.erb b/app/views/goals/_pending_pledge_banner.html.erb index 5b716a5c9..0a0049083 100644 --- a/app/views/goals/_pending_pledge_banner.html.erb +++ b/app/views/goals/_pending_pledge_banner.html.erb @@ -10,7 +10,7 @@ <%= icon("info", size: "sm") %>

- <%= title %> + <%= title %> · <%= t("goals.show.pending_pledge.pledged_at", time_ago: time_ago_in_words(pledge.created_at)) %>

<%= t(body_key) %>

diff --git a/app/views/goals/_status_callout.html.erb b/app/views/goals/_status_callout.html.erb index 2c6521d74..a2c609ad3 100644 --- a/app/views/goals/_status_callout.html.erb +++ b/app/views/goals/_status_callout.html.erb @@ -24,5 +24,5 @@ <%= icon(icon_glyph, size: "sm") %> <%= label %> · - <%= context %> + "><%= context %>
diff --git a/app/views/goals/show.html.erb b/app/views/goals/show.html.erb index 7036e1638..21bc8ec22 100644 --- a/app/views/goals/show.html.erb +++ b/app/views/goals/show.html.erb @@ -5,7 +5,7 @@

<%= @goal.name %>

-

<%= @goal.header_summary %>

+

<%= @goal.header_summary %>

<% last_days = @goal.last_matched_pledge_days_ago %> <% unless last_days.nil? %>

@@ -122,7 +122,7 @@ <%= render Goals::ProgressRingComponent.new(goal: @goal, size: 180) %>

<%= @goal.current_balance_money.format(precision: 0) %>

<% unless @goal.completed? %> -

<%= t(".ring.to_go", amount: @goal.remaining_amount_money.format(precision: 0)) %>

+

<%= t(".ring.to_go", amount: @goal.remaining_amount_money.format(precision: 0)) %>

<% end %> <% unless @goal.completed? || @goal.status == :reached || @goal.paused? || @goal.archived? %> <%# Single Record pledge entry point on the page. Pre-filled with the @@ -149,7 +149,7 @@

<%= t(@goal.archived? ? ".inactive.heading_archived" : ".inactive.heading_paused") %>

-

+

<%= t(".inactive.body", saved: @goal.current_balance_money.format(precision: 0), target: @goal.target_amount_money.format(precision: 0)) %>

@@ -159,7 +159,7 @@ <%= icon("party-popper", size: "2xl", color: "success") %>

<%= t(".celebration.heading") %>

-

<%= t(".celebration.body", saved: @goal.current_balance_money.format(precision: 0), target: @goal.target_amount_money.format(precision: 0)) %>

+

<%= t(".celebration.body", saved: @goal.current_balance_money.format(precision: 0), target: @goal.target_amount_money.format(precision: 0)) %>

<% if @goal.may_archive? %>
<%= render DS::Button.new( @@ -199,7 +199,7 @@

<%= t(".projection.heading") %>

<%= sanitize @goal.projection_summary %>

<% if @goal.status == :behind && @goal.monthly_target_amount %> -

+

<%= t("goals.show.catch_up.body", avg: @goal.pace_money.format(precision: 0), required: Money.new(@goal.monthly_target_amount, @goal.currency).format(precision: 0)) %>

<% end %>