mirror of
https://github.com/we-promise/sure.git
synced 2026-09-06 15:14:19 +00:00
* fix(goals): make the figure beside the ring agree with the ring Recording a spend left the goal page contradicting itself. The ring is drawn from `progress_percent`, which counts money still held plus money already spent on the goal; the figure beside it showed only the first half. A goal that had saved 5,000 and spent 2,000 of it rendered a 100% ring next to "3,000 of 5,000" — two answers on one card, with nothing to say which to believe. The model was never wrong: `progress_percent` and `remaining_amount` have both counted the two halves since the spend feature landed. Only the display took one of them. `progress_amount` names what progress actually counts, and both surfaces now read from it. The amount already used is reported as part of that total rather than beside it — "Including 2,000 already used" — so a reader has nothing to add up and no reason to read a completed goal as a shortfall. "Used" rather than "spent", matching the menu entry the user came through: it is the same gesture, and spending on the thing you saved for is the goal working, not failing. Shown only where there is something to show. The overwhelming majority of goals never record a spend, and a permanent "0 used" line would be noise on every card. A reserve refuses consumption outright, so this never appears on one. What is still sitting in each account keeps its place in the funding breakdown, which is where that question belongs — the view test asserts the headline specifically rather than the whole page, for exactly that reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye * fix(goals): make the ring announce what it shows Review on #3213. The headline moved to the progress total; the ring's `aria-label` still read `current_balance_money`. A screen reader announced "$3,000 of $5,000 saved" while the line beside it said "$5,000, including $2,000 already used" — the same ring, two different numbers depending on whether you could see it. The wording moves with the figure. "Saved" stops being the whole truth once part of the total has been spent on the goal, so a goal that has recorded one gets the sentence that says so, and every other goal keeps the wording it had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye * style(goals): use the component's t() for the ring's labels Review on #3213. `I18n.t` works, but the component helper is what the rest of the codebase reaches for and it carries the view's locale context. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
64 lines
3.3 KiB
ERB
64 lines
3.3 KiB
ERB
<div class="group relative bg-container rounded-xl shadow-border-xs hover:bg-container-hover transition-colors p-6 <%= "opacity-75" if goal.paused? || goal.archived? %>"
|
|
<% if filterable %> data-goals-filter-target="card"<% end %>
|
|
data-goal-name="<%= goal.name %>"
|
|
data-goal-status="<%= goal.display_status %>">
|
|
<div class="flex items-start gap-3">
|
|
<%= render Goals::AvatarComponent.new(goal: goal, size: "lg") %>
|
|
<div class="min-w-0 flex-1">
|
|
<%# Name owns its row at full width — the status pill moved to the meta
|
|
line below so it stops eating the title space on narrow cards. %>
|
|
<p class="text-base font-medium text-primary truncate">
|
|
<a href="<%= goal_path(goal) %>"
|
|
aria-label="<%= aria_label %>"
|
|
class="before:absolute before:inset-0 before:rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-100">
|
|
<%= goal.name %>
|
|
</a>
|
|
</p>
|
|
<div class="flex items-center gap-2 mt-0.5">
|
|
<%= render Goals::StatusPillComponent.new(goal: goal) %>
|
|
<% if secondary_line.present? %>
|
|
<p class="min-w-0 text-xs text-subdued truncate"><%= secondary_line %></p>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<%# shrink-0 keeps the ring at full size; the name block shrinks first. %>
|
|
<div class="shrink-0">
|
|
<%= render DS::ProgressRing.new(percent: progress_percent, tone: ring_tone) %>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-5">
|
|
<%# The figure the ring is drawn from, not the account balance: money still
|
|
held plus money already spent on the goal itself. Showing the balance
|
|
alone put a 100% ring beside "3,000 of 5,000". %>
|
|
<div class="flex items-baseline gap-1.5">
|
|
<span class="text-xl font-medium text-primary tabular-nums privacy-sensitive"><%= goal.progress_amount_money.format(precision: 0) %></span>
|
|
<span class="text-xs text-subdued tabular-nums privacy-sensitive">/ <%= goal.target_amount_money.format(precision: 0) %></span>
|
|
</div>
|
|
<% if goal.any_consumption? %>
|
|
<%# "Including", not a second figure beside the first: the amount is part
|
|
of the total above, and a reader should not have to add anything. %>
|
|
<p class="text-xs text-subdued tabular-nums mt-1 privacy-sensitive">
|
|
<%= t("goals.goal_card.including_used", amount: goal.consumed_amount_money.format(precision: 0)) %>
|
|
</p>
|
|
<% end %>
|
|
<% if pace_line %>
|
|
<p class="text-xs text-subdued tabular-nums mt-1 privacy-sensitive"><%= pace_line %></p>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%# flex-wrap + gap so the account label and the footer line wrap onto
|
|
separate lines under a gap when cramped, instead of butting together
|
|
(the "1 accountcatch up" run-on at narrow card widths). %>
|
|
<div class="mt-4 flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
<%= render Goals::AccountStackComponent.new(accounts: linked_accounts, color_map: goal.account_color_map) %>
|
|
<span class="text-xs text-subdued whitespace-nowrap"><%= linked_accounts_count_label %></span>
|
|
</div>
|
|
<span class="text-xs text-subdued tabular-nums <%= "privacy-sensitive" if footer_has_money? %>">
|
|
<%= footer_line %><% if has_pending_pledge? %> · <%= t("goals.goal_card.pending_count", count: pending_pledges_count) %><% end %>
|
|
</span>
|
|
</div>
|
|
</div>
|